├── .autorelease.yml ├── .bmp.yml ├── .circleci └── config.yml ├── .editorconfig ├── .gitignore ├── .releaseignore ├── README.litcoffee ├── bin └── create-master-json ├── gulpfile.coffee ├── package.json ├── spec ├── base-domain-loopback.coffee ├── create-facade.coffee ├── domains │ ├── medical │ │ ├── hospital-repository.coffee │ │ ├── hospital.coffee │ │ ├── medicine-repository.coffee │ │ ├── medicine.coffee │ │ ├── patient-repository.coffee │ │ └── patient.coffee │ └── music-live │ │ ├── instrument-factory.coffee │ │ ├── instrument-repository.coffee │ │ ├── instrument.coffee │ │ ├── live-info-factory.coffee │ │ ├── live-info.coffee │ │ ├── player-factory.coffee │ │ ├── player-repository.coffee │ │ ├── player.coffee │ │ ├── set-list.coffee │ │ ├── song-factory.coffee │ │ ├── song-repository.coffee │ │ ├── song.coffee │ │ └── staff.coffee ├── global.js ├── lib │ ├── loopback-domain-facade.coffee │ ├── loopback-relation-repository.coffee │ ├── loopback-repository.coffee │ ├── loopback-user-repository.coffee │ ├── model-definition.coffee │ └── setting-exporter.coffee └── system-test.coffee ├── src ├── lib │ ├── loopback-domain-facade.coffee │ ├── loopback-relation-repository.coffee │ ├── loopback-repository.coffee │ ├── loopback-user-repository.coffee │ ├── model-definition.coffee │ ├── relation-name.coffee │ └── setting-exporter.coffee ├── main-browser.coffee └── main.coffee └── webpack-test ├── index.js └── webpack.config.js /.autorelease.yml: -------------------------------------------------------------------------------- 1 | hooks: 2 | release: 3 | pre: 4 | - gulp coffee 5 | gh_pages: 6 | pre: 7 | - gulp yuidoc 8 | 9 | config: 10 | git_user_name: CircleCI 11 | git_user_email: circleci@example.com 12 | npm_update_depth: 0 13 | version_prefix: v 14 | create_branch: false 15 | npm_shrinkwrap: false 16 | create_gh_pages: true 17 | gh_pages_dir: doc 18 | circle: 19 | machine: 20 | node: 21 | version: 4.4.5 22 | -------------------------------------------------------------------------------- /.bmp.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 4.5.0 3 | commit: Bump to version v%.%.% 4 | files: 5 | package.json: '"version": "%.%.%",' 6 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | test: 4 | docker: 5 | - image: 'circleci/node:6' 6 | working_directory: ~/repo 7 | steps: 8 | - run: 'echo ''export PATH=./node_modules/.bin:$PATH'' >> $BASH_ENV' 9 | - checkout 10 | - run: git config --global user.name "CircleCI" 11 | - run: git config --global user.email "circleci@example.com" 12 | - run: npm install 13 | - run: nca run nca notice update-modules 14 | - run: npm test 15 | deploy: 16 | docker: 17 | - image: 'circleci/node:4' 18 | working_directory: ~/repo 19 | steps: 20 | - run: 'echo ''export PATH=./node_modules/.bin:$PATH'' >> $BASH_ENV' 21 | - checkout 22 | - run: git config --global user.name "CircleCI" 23 | - run: git config --global user.email "circleci@example.com" 24 | - run: npm install 25 | - run: nca run nca notice update-modules 26 | - run: npm test 27 | - run: npm install 28 | - run: nca run gulp coffee 29 | - run: nca release --prefix v 30 | - run: nca run gulp yuidoc 31 | - run: nca run nca gh-pages --dir doc 32 | workflows: 33 | version: 2 34 | test_deploy: 35 | jobs: 36 | - test: 37 | filters: 38 | branches: 39 | ignore: 40 | - gh-pages 41 | - /release.*/ 42 | - deploy: 43 | filters: 44 | branches: 45 | only: 46 | - master 47 | requires: 48 | - test 49 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | root = true 3 | 4 | [*.coffee] 5 | 6 | indent_size = 4 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [package.json] 11 | indent_size = 2 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.csv 2 | *.dat 3 | *.iml 4 | *.log 5 | *.out 6 | *.pid 7 | *.seed 8 | *.sublime-* 9 | *.swo 10 | *.swp 11 | *.tgz 12 | *.xml 13 | .DS_Store 14 | .idea 15 | .project 16 | .strong-pm 17 | coverage 18 | node_modules 19 | npm-debug.log 20 | dist 21 | doc 22 | 23 | # master data 24 | all.json 25 | -------------------------------------------------------------------------------- /.releaseignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .editorconfig 3 | spec 4 | test 5 | .releaseignore 6 | .bmp.yml 7 | npm-debug.log 8 | 9 | Gruntfile.coffee 10 | README.litcoffee 11 | circle.yml 12 | 13 | 14 | # master data 15 | all.json 16 | -------------------------------------------------------------------------------- /README.litcoffee: -------------------------------------------------------------------------------- 1 | # base-domain-loopback 2 | 3 | Domain-Driven Design with [loopback](http://docs.strongloop.com/display/public/LB/LoopBack) 4 | 5 | # API documentation 6 | 7 | [latest API documentation Page](http://cureapp.github.io/base-domain-loopback/index.html) 8 | 9 | extends [base-domain](https://github.com/cureapp/base-domain) 10 | 11 | # installation 12 | 13 | ```bash 14 | $ npm install base-domain-loopback 15 | ``` 16 | 17 | 18 | # usage 19 | ## definition 20 | 21 | model definition is the same as [base-domain](https://github.com/cureapp/base-domain) 22 | 23 | domain-dir/player.coffee 24 | 25 | Domain = require('base-domain-loopback') 26 | 27 | class Player extends Domain.Entity 28 | @properties: 29 | name: @TYPES.STRING 30 | 31 | module.exports = Player 32 | 33 | domain-dir/player-repository.coffee 34 | 35 | Domain = require('base-domain-loopback') 36 | 37 | class PlayerRepository extends Domain.LoopbackUserRepository 38 | @aclType: 'owner' # access type. see README in loopback-with-admin 39 | 40 | module.exports = PlayerRepository 41 | 42 | main.coffee 43 | 44 | domain = require('base-domain-loopback').createInstance 45 | dirname: 'domain-dir' 46 | baseURL: 'localhost:4157/api' 47 | 48 | 49 | ## run loopback server with loopback-with-admin 50 | 51 | domain = require('base-domain-loopback').createInstance dirname: 'domain-dir' 52 | 53 | modelDefinitions = domain.getModelDefinitions() 54 | 55 | config = 56 | server: 57 | port: 4157 58 | 59 | require('loopback-with-admin').run(modelDefinitions, config) 60 | -------------------------------------------------------------------------------- /bin/create-master-json: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | require('../node_modules/.bin/bd-create-master'); 4 | -------------------------------------------------------------------------------- /gulpfile.coffee: -------------------------------------------------------------------------------- 1 | gulp = require 'gulp' 2 | coffee = require 'gulp-coffee' 3 | yuidoc = require 'gulp-yuidoc' 4 | 5 | 6 | gulp.task 'coffee', -> 7 | 8 | gulp.src 'src/**/*.coffee' 9 | .pipe(coffee bare: true) 10 | .pipe(gulp.dest 'dist') 11 | 12 | 13 | gulp.task 'yuidoc', -> 14 | 15 | gulp.src ['src/**/*.coffee', 'node_modules/base-domain/src/lib/**/*.coffee'] 16 | .pipe(yuidoc({ 17 | syntaxtype: 'coffee' 18 | project: 19 | name: 'base-domain-loopback' 20 | })) 21 | .pipe(gulp.dest('doc')) 22 | .on('error', console.log) 23 | 24 | module.exports = gulp 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "base-domain-loopback", 3 | "version": "4.5.0", 4 | "description": "## creates custom model-definitions for loopback-with-admin", 5 | "main": "./dist/main.js", 6 | "browser": { 7 | "./dist/main.js": "./dist/main-browser.js" 8 | }, 9 | "engines": { 10 | "node": ">=0.12" 11 | }, 12 | "directories": { 13 | "test": "spec/" 14 | }, 15 | "bin": { 16 | "bd-create-master": "bin/create-master-json" 17 | }, 18 | "scripts": { 19 | "test": "mocha -r spec/global.js spec/*.coffee spec/lib/*.coffee && gulp coffee && npm run wp", 20 | "wp": "webpack --config webpack-test/webpack.config.js" 21 | }, 22 | "dependencies": { 23 | "base-domain": "^5.1.6", 24 | "debug": "^2.2.0", 25 | "loopback-promised": "~1.3.2", 26 | "moment": "^2.19.1" 27 | }, 28 | "author": "CureApp, Inc.", 29 | "devDependencies": { 30 | "coffee-script": "^1.9.3", 31 | "espower-coffee": "^1.0.0", 32 | "gulp": "^3.9.1", 33 | "gulp-coffee": "^2.3.1", 34 | "gulp-yuidoc": "^0.1.2", 35 | "loopback-with-admin": "~2.0.0", 36 | "mocha": "^2.3.3", 37 | "node-circleci-autorelease": "^2.1.7", 38 | "power-assert": "^1.1.0", 39 | "webpack": "^1.13.1" 40 | }, 41 | "repository": { 42 | "type": "git", 43 | "url": "https://github.com/CureApp/base-domain-loopback.git" 44 | }, 45 | "bugs": { 46 | "url": "https://github.com/CureApp/base-domain-loopback/issues" 47 | }, 48 | "homepage": "https://github.com/CureApp/base-domain-loopback", 49 | "license": "ISC" 50 | } 51 | -------------------------------------------------------------------------------- /spec/base-domain-loopback.coffee: -------------------------------------------------------------------------------- 1 | 2 | module.exports = require('../src/main') 3 | -------------------------------------------------------------------------------- /spec/create-facade.coffee: -------------------------------------------------------------------------------- 1 | 2 | module.exports = 3 | create: (options) -> require('./base-domain-loopback').createInstance(options) 4 | -------------------------------------------------------------------------------- /spec/domains/medical/hospital-repository.coffee: -------------------------------------------------------------------------------- 1 | LoopbackRepository = require('../../base-domain-loopback').LoopbackRepository 2 | 3 | ###* 4 | a repository for hospital 5 | 6 | @class HospitalRepository 7 | @extends LoopbackRepository 8 | ### 9 | class HospitalRepository extends LoopbackRepository 10 | ###* 11 | model name to create 12 | 13 | @property modelName 14 | @static 15 | @protected 16 | @type String 17 | ### 18 | @modelName: 'hospital' 19 | 20 | module.exports = HospitalRepository 21 | -------------------------------------------------------------------------------- /spec/domains/medical/hospital.coffee: -------------------------------------------------------------------------------- 1 | Entity = require('base-domain').Entity 2 | 3 | ###* 4 | entity class for hospital 5 | 6 | @class Hospital 7 | @extends Entity 8 | ### 9 | class Hospital extends Entity 10 | 11 | ###* 12 | property types 13 | key: property name 14 | value: type 15 | 16 | @property properties 17 | @static 18 | @protected 19 | @type Object 20 | ### 21 | @properties: 22 | name: @TYPES.STRING 23 | 24 | module.exports = Hospital 25 | -------------------------------------------------------------------------------- /spec/domains/medical/medicine-repository.coffee: -------------------------------------------------------------------------------- 1 | LoopbackRepository = require('../../base-domain-loopback').LoopbackRepository 2 | 3 | ###* 4 | a repository for medicine 5 | 6 | @class MedicineRepository 7 | @extends LoopbackRepository 8 | ### 9 | class MedicineRepository extends LoopbackRepository 10 | ###* 11 | model name to create 12 | 13 | @property modelName 14 | @static 15 | @protected 16 | @type String 17 | ### 18 | @modelName: 'medicine' 19 | 20 | module.exports = MedicineRepository 21 | -------------------------------------------------------------------------------- /spec/domains/medical/medicine.coffee: -------------------------------------------------------------------------------- 1 | Entity = require('base-domain').Entity 2 | 3 | ###* 4 | entity class for medicine 5 | 6 | @class Medicine 7 | @extends Entity 8 | ### 9 | class Medicine extends Entity 10 | 11 | ###* 12 | property types 13 | key: property name 14 | value: type 15 | 16 | @property properties 17 | @static 18 | @protected 19 | @type Object 20 | ### 21 | @properties: 22 | name: @TYPES.STRING 23 | patient: @TYPES.MODEL 'patient', 24 | isOutOfAggregate: true 25 | hospital: @TYPES.MODEL 'hospital', 26 | isOutOfAggregate: true 27 | 28 | module.exports = Medicine 29 | -------------------------------------------------------------------------------- /spec/domains/medical/patient-repository.coffee: -------------------------------------------------------------------------------- 1 | LoopbackRepository = require('../../base-domain-loopback').LoopbackRepository 2 | 3 | ###* 4 | a repository for patient 5 | 6 | @class PatientRepository 7 | @extends LoopbackRepository 8 | ### 9 | class PatientRepository extends LoopbackRepository 10 | ###* 11 | model name to create 12 | 13 | @property modelName 14 | @static 15 | @protected 16 | @type String 17 | ### 18 | @modelName: 'patient' 19 | 20 | module.exports = PatientRepository 21 | -------------------------------------------------------------------------------- /spec/domains/medical/patient.coffee: -------------------------------------------------------------------------------- 1 | Entity = require('base-domain').Entity 2 | Hospital = require('./hospital') 3 | 4 | ###* 5 | entity class for patient 6 | 7 | @class Patient 8 | @extends Entity 9 | ### 10 | class Patient extends Entity 11 | 12 | ###* 13 | property types 14 | key: property name 15 | value: type 16 | 17 | @property properties 18 | @static 19 | @protected 20 | @type Object 21 | ### 22 | @properties: 23 | name: @TYPES.STRING 24 | hospital: @TYPES.MODEL 'hospital', 25 | isOutOfAggregate: true 26 | 27 | module.exports = Patient 28 | -------------------------------------------------------------------------------- /spec/domains/music-live/instrument-factory.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | generated by base-domain generator 3 | ### 4 | 5 | 6 | BaseFactory = require('base-domain').BaseFactory 7 | 8 | 9 | ###* 10 | factory of instrument 11 | 12 | @class InstrumentFactory 13 | @extends BaseFactory 14 | ### 15 | class InstrumentFactory extends BaseFactory 16 | 17 | ###* 18 | model name to create 19 | 20 | @property modelName 21 | @static 22 | @protected 23 | @type String 24 | ### 25 | @modelName: 'instrument' 26 | 27 | module.exports = InstrumentFactory 28 | -------------------------------------------------------------------------------- /spec/domains/music-live/instrument-repository.coffee: -------------------------------------------------------------------------------- 1 | 2 | BaseDomainLoopback = require('../../base-domain-loopback') 3 | 4 | 5 | ###* 6 | repository of instrument 7 | 8 | @class InstrumentRepository 9 | @extends LoopbackRepository 10 | ### 11 | class InstrumentRepository extends BaseDomainLoopback.LoopbackRepository 12 | 13 | ###* 14 | model name to create 15 | 16 | @property modelName 17 | @static 18 | @protected 19 | @type String 20 | ### 21 | @modelName: 'instrument' 22 | 23 | module.exports = InstrumentRepository 24 | -------------------------------------------------------------------------------- /spec/domains/music-live/instrument.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | generated by base-domain generator 3 | ### 4 | 5 | Entity = require('base-domain').Entity 6 | 7 | ###* 8 | entity class of instrument 9 | 10 | @class Instrument 11 | @extends Entity 12 | ### 13 | class Instrument extends Entity 14 | 15 | ###* 16 | property types 17 | key: property name 18 | value: type 19 | 20 | @property properties 21 | @static 22 | @protected 23 | @type Object 24 | ### 25 | @properties: 26 | name : @TYPES.STRING 27 | ### examples 28 | age : @TYPES.NUMBER 29 | confirmed : @TYPES.BOOLEAN 30 | confirmedAt : @TYPES.DATE 31 | team : @TYPES.MODEL 'team' 32 | extraTeam : @TYPES.MODEL 'team', 'exTeamId' 33 | otherInfo : @TYPES.OBJECT 34 | createdAt : @TYPES.CREATED_AT 35 | updatedAt : @TYPES.UPDATED_AT 36 | temporary : @TYPES.TMP # temporary prop, removed in toPlainObject() 37 | tmpObj : @TYPES.TMP 'OBJECT' 38 | ### 39 | 40 | module.exports = Instrument 41 | -------------------------------------------------------------------------------- /spec/domains/music-live/live-info-factory.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | generated by base-domain generator 3 | ### 4 | 5 | 6 | BaseFactory = require('base-domain').BaseFactory 7 | 8 | 9 | ###* 10 | factory of live-info 11 | 12 | @class LiveInfoFactory 13 | @extends BaseFactory 14 | ### 15 | class LiveInfoFactory extends BaseFactory 16 | 17 | ###* 18 | model name to create 19 | 20 | @property modelName 21 | @static 22 | @protected 23 | @type String 24 | ### 25 | @modelName: 'live-info' 26 | 27 | module.exports = LiveInfoFactory 28 | -------------------------------------------------------------------------------- /spec/domains/music-live/live-info.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | generated by base-domain generator 3 | ### 4 | 5 | BaseModel = require('base-domain').BaseModel 6 | 7 | ###* 8 | entity class of live-info 9 | 10 | @class LiveInfo 11 | @extends BaseModel 12 | ### 13 | class LiveInfo extends BaseModel 14 | 15 | ###* 16 | property types 17 | key: property name 18 | value: type 19 | 20 | @property properties 21 | @static 22 | @protected 23 | @type Object 24 | ### 25 | @properties: 26 | name : @TYPES.STRING 27 | ### examples 28 | age : @TYPES.NUMBER 29 | confirmed : @TYPES.BOOLEAN 30 | confirmedAt : @TYPES.DATE 31 | team : @TYPES.MODEL 'team' 32 | extraTeam : @TYPES.MODEL 'team', 'exTeamId' 33 | otherInfo : @TYPES.OBJECT 34 | createdAt : @TYPES.CREATED_AT 35 | updatedAt : @TYPES.UPDATED_AT 36 | temporary : @TYPES.TMP # temporary prop, removed in toPlainObject() 37 | tmpObj : @TYPES.TMP 'OBJECT' 38 | ### 39 | 40 | module.exports = LiveInfo 41 | -------------------------------------------------------------------------------- /spec/domains/music-live/player-factory.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | generated by base-domain generator 3 | ### 4 | 5 | 6 | BaseFactory = require('base-domain').BaseFactory 7 | 8 | 9 | ###* 10 | factory of player 11 | 12 | @class PlayerFactory 13 | @extends BaseFactory 14 | ### 15 | class PlayerFactory extends BaseFactory 16 | 17 | ###* 18 | model name to create 19 | 20 | @property modelName 21 | @static 22 | @protected 23 | @type String 24 | ### 25 | @modelName: 'player' 26 | 27 | module.exports = PlayerFactory 28 | -------------------------------------------------------------------------------- /spec/domains/music-live/player-repository.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | generated by base-domain generator 3 | ### 4 | 5 | BaseDomainLoopback = require('../../base-domain-loopback') 6 | ###* 7 | repository of player 8 | 9 | @class PlayerRepository 10 | @extends LoopbackUserRepository 11 | ### 12 | class PlayerRepository extends BaseDomainLoopback.LoopbackUserRepository 13 | 14 | @lbDefinitions: 15 | ttl : 24 * 3600 * 365 * 10 16 | maxTTL : 24 * 3600 * 365 * 100 17 | 18 | @aclType: 'owner' 19 | 20 | ###* 21 | model name to create 22 | 23 | @property modelName 24 | @static 25 | @protected 26 | @type String 27 | ### 28 | @modelName: 'player' 29 | 30 | module.exports = PlayerRepository 31 | -------------------------------------------------------------------------------- /spec/domains/music-live/player.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | generated by base-domain generator 3 | ### 4 | 5 | Entity = require('base-domain').Entity 6 | 7 | ###* 8 | entity class of player 9 | 10 | @class Player 11 | @extends Entity 12 | ### 13 | class Player extends Entity 14 | 15 | ###* 16 | property types 17 | key: property name 18 | value: type 19 | 20 | @property properties 21 | @static 22 | @protected 23 | @type Object 24 | ### 25 | @properties: 26 | name : @TYPES.STRING 27 | ### examples 28 | age : @TYPES.NUMBER 29 | confirmed : @TYPES.BOOLEAN 30 | confirmedAt : @TYPES.DATE 31 | team : @TYPES.MODEL 'team' 32 | extraTeam : @TYPES.MODEL 'team', 'exTeamId' 33 | otherInfo : @TYPES.OBJECT 34 | createdAt : @TYPES.CREATED_AT 35 | updatedAt : @TYPES.UPDATED_AT 36 | temporary : @TYPES.TMP # temporary prop, removed in toPlainObject() 37 | tmpObj : @TYPES.TMP 'OBJECT' 38 | ### 39 | 40 | module.exports = Player 41 | -------------------------------------------------------------------------------- /spec/domains/music-live/set-list.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | generated by base-domain generator 3 | ### 4 | 5 | BaseList = require('base-domain').BaseList 6 | 7 | ###* 8 | entity class of set-list 9 | 10 | @class SetList 11 | @extends Entity 12 | ### 13 | class SetList extends BaseList 14 | 15 | @itemModelName: 'song' 16 | 17 | module.exports = SetList 18 | -------------------------------------------------------------------------------- /spec/domains/music-live/song-factory.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | generated by base-domain generator 3 | ### 4 | 5 | 6 | BaseFactory = require('base-domain').BaseFactory 7 | 8 | 9 | ###* 10 | factory of song 11 | 12 | @class SongFactory 13 | @extends BaseFactory 14 | ### 15 | class SongFactory extends BaseFactory 16 | 17 | ###* 18 | model name to create 19 | 20 | @property modelName 21 | @static 22 | @protected 23 | @type String 24 | ### 25 | @modelName: 'song' 26 | 27 | module.exports = SongFactory 28 | -------------------------------------------------------------------------------- /spec/domains/music-live/song-repository.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | generated by base-domain generator 3 | ### 4 | 5 | 6 | 7 | BaseDomainLoopback = require('../../base-domain-loopback') 8 | 9 | ###* 10 | repository of song 11 | 12 | @class SongRepository 13 | @extends LoopbackRelationRepository 14 | ### 15 | class SongRepository extends BaseDomainLoopback.LoopbackRelationRepository 16 | @aclType: 'owner' 17 | @belongsTo: 'author' 18 | 19 | ###* 20 | model name to create 21 | 22 | @property modelName 23 | @static 24 | @protected 25 | @type String 26 | ### 27 | @modelName: 'song' 28 | 29 | module.exports = SongRepository 30 | -------------------------------------------------------------------------------- /spec/domains/music-live/song.coffee: -------------------------------------------------------------------------------- 1 | ### 2 | generated by base-domain generator 3 | ### 4 | 5 | Entity = require('base-domain').Entity 6 | 7 | ###* 8 | entity class of song 9 | 10 | @class Song 11 | @extends Entity 12 | ### 13 | class Song extends Entity 14 | 15 | ###* 16 | property types 17 | key: property name 18 | value: type 19 | 20 | @property properties 21 | @static 22 | @protected 23 | @type Object 24 | ### 25 | @properties: 26 | name : @TYPES.STRING 27 | author : @TYPES.MODEL 'player', 'authorId' 28 | ### examples 29 | age : @TYPES.NUMBER 30 | confirmed : @TYPES.BOOLEAN 31 | confirmedAt : @TYPES.DATE 32 | team : @TYPES.MODEL 'team' 33 | extraTeam : @TYPES.MODEL 'team', 'exTeamId' 34 | otherInfo : @TYPES.OBJECT 35 | createdAt : @TYPES.CREATED_AT 36 | updatedAt : @TYPES.UPDATED_AT 37 | temporary : @TYPES.TMP # temporary prop, removed in toPlainObject() 38 | tmpObj : @TYPES.TMP 'OBJECT' 39 | ### 40 | 41 | module.exports = Song 42 | -------------------------------------------------------------------------------- /spec/domains/music-live/staff.coffee: -------------------------------------------------------------------------------- 1 | 2 | { Entity } = require 'base-domain' 3 | 4 | class Staff extends Entity 5 | 6 | @properties: 7 | name: @TYPES.STRING 8 | role: @TYPES.STRING 9 | 10 | module.exports = Staff 11 | -------------------------------------------------------------------------------- /spec/global.js: -------------------------------------------------------------------------------- 1 | assert = require('power-assert'); 2 | require('espower-coffee/guess'); 3 | -------------------------------------------------------------------------------- /spec/lib/loopback-domain-facade.coffee: -------------------------------------------------------------------------------- 1 | 2 | LoopbackDomainFacade = require('../../src/lib/loopback-domain-facade') 3 | 4 | describe 'LoopbackDomainFacade', -> 5 | 6 | beforeEach -> 7 | @domain = new LoopbackDomainFacade() 8 | 9 | it 'has "debug" property (default: false)', -> 10 | assert @domain.debug is false 11 | 12 | 13 | it 'has "debug" property, true if option.debug is true', -> 14 | domain = new LoopbackDomainFacade(debug: true) 15 | assert domain.debug 16 | 17 | 18 | it 'has loopback promised', -> 19 | assert @domain.lbPromised? 20 | assert @domain.lbPromised instanceof require('loopback-promised') 21 | 22 | 23 | it 'has loopback promised with baseURL', -> 24 | domain = new LoopbackDomainFacade(baseURL: 'localhost') 25 | assert domain.lbPromised.baseURL is 'localhost' 26 | 27 | 28 | it 'has sessionId if set', -> 29 | domain = new LoopbackDomainFacade(sessionId: 'ab/c') 30 | assert domain.sessionId is 'ab/c' 31 | 32 | 33 | describe 'setSessionId', -> 34 | it 'sets sessionId to this instance', -> 35 | domain = new LoopbackDomainFacade(sessionId: 'ab/c') 36 | domain.setSessionId('bc/d') 37 | assert domain.sessionId is 'bc/d' 38 | 39 | 40 | describe 'setBaseURL', -> 41 | it 'sets baseURL to lbPromised', -> 42 | domain = new LoopbackDomainFacade(baseURL: 'localhost') 43 | domain.setBaseURL('localhost:3000/api') 44 | assert domain.lbPromised.baseURL is 'localhost:3000/api' 45 | 46 | -------------------------------------------------------------------------------- /spec/lib/loopback-relation-repository.coffee: -------------------------------------------------------------------------------- 1 | 2 | { LoopbackRelationRepository, LoopbackRepository, Entity } = require('../base-domain-loopback') 3 | { LoopbackRelatedClient } = require('loopback-promised') 4 | 5 | 6 | describe 'LoopbackRelationRepository', -> 7 | 8 | beforeEach -> 9 | 10 | @domain = require('../create-facade').create() 11 | 12 | class SampleModel extends Entity 13 | @properties: 14 | date: @TYPES.DATE 15 | parent: @TYPES.MODEL 'parent-model' 16 | 17 | class ParentModel extends Entity 18 | @properties: 19 | name: @TYPES.STRING 20 | 21 | class SampleModelRepository extends LoopbackRelationRepository 22 | @modelName: 'sample-model' 23 | @belongsTo: 'parent' 24 | 25 | class ParentModelRepository extends LoopbackRepository 26 | @modelName: 'parent-model' 27 | 28 | @domain.addClass('sample-model', SampleModel) 29 | @domain.addClass('parent-model', ParentModel) 30 | @domain.addClass('sample-model-repository', SampleModelRepository) 31 | @domain.addClass('parent-model-repository', ParentModelRepository) 32 | 33 | 34 | 35 | it 'has @belongsTo', -> 36 | assert LoopbackRelationRepository.hasOwnProperty 'belongsTo' 37 | 38 | 39 | it 'cannot be created when "belongsTo" is not set', -> 40 | class Repo extends LoopbackRelationRepository 41 | @modelName: 'sample-model' 42 | @domain.addClass('sample-model-repository', Repo) 43 | 44 | assert.throws (=> new Repo({}, @domain)), 'You must set @belongsTo and @foreignKeyName when extending RelationRepository.' 45 | 46 | 47 | it 'cannot be create when "belongsTo" is not a prop name', -> 48 | class Repo extends LoopbackRelationRepository 49 | @modelName: 'sample-model' 50 | @belongsTo: 'parent-model' 51 | 52 | @domain.addClass('sample-model-repository', Repo) 53 | 54 | assert.throws (=> new Repo({}, @domain)), '"belongsTo" property: parent-model is not an entity prop.' 55 | 56 | 57 | it 'is created when "belongsTo" is set', -> 58 | @domain.createRepository('sample-model') 59 | 60 | it 'has foreignKeyName', -> 61 | repo = @domain.createRepository('sample-model') 62 | 63 | assert repo.foreignKeyName is 'parentModelId' 64 | 65 | it 'has relClient', -> 66 | repo = @domain.createRepository('sample-model', timeout: 300) 67 | 68 | assert repo.relClient? 69 | assert repo.relClient instanceof LoopbackRelatedClient 70 | assert repo.relClient.timeout is 300 71 | 72 | describe 'getClientByEntity', -> 73 | 74 | it 'returns relClient when it contains foreign key', -> 75 | repo = @domain.createRepository('sample-model') 76 | entity = @domain.createModel 'sample-model', 77 | date: '1998-03-21' 78 | parent: 79 | id: 'pnt' 80 | name: 'pnt-name' 81 | 82 | assert repo.getClientByEntity(entity) is repo.relClient 83 | 84 | it 'returns client when it does not contain foreign key', -> 85 | repo = @domain.createRepository('sample-model') 86 | entity = @domain.createModel 'sample-model', 87 | date: '1998-03-21' 88 | parent: 89 | name: 'pnt-name' 90 | 91 | assert repo.getClientByEntity(entity) is repo.client 92 | 93 | 94 | describe 'getClientByForeignKey', -> 95 | 96 | it 'returns relClient when foreign key is passed', -> 97 | repo = @domain.createRepository('sample-model') 98 | assert repo.getClientByForeignKey(0) is repo.relClient 99 | 100 | it 'returns relClient when foreign key is not passed', -> 101 | repo = @domain.createRepository('sample-model') 102 | assert repo.getClientByForeignKey(null) is repo.client 103 | 104 | 105 | describe 'getClientByQuery', -> 106 | 107 | it 'returns relClient when query.where contains foreignKey and the value is not object', -> 108 | repo = @domain.createRepository('sample-model') 109 | where = 110 | parentModelId: 123 111 | 112 | assert repo.getClientByQuery(where: where) is repo.relClient 113 | 114 | it 'returns client when does not contain where', -> 115 | repo = @domain.createRepository('sample-model') 116 | assert repo.getClientByQuery({}) is repo.client 117 | 118 | it 'returns client when query.where contains foreignKey but the value is object', -> 119 | repo = @domain.createRepository('sample-model') 120 | where = 121 | parentModelId: gte: 122 122 | assert repo.getClientByQuery(where: where) is repo.client 123 | 124 | it 'returns client when query.where does not contain foreignKey', -> 125 | repo = @domain.createRepository('sample-model') 126 | where = 127 | and: [ 128 | parentModelId: 123 129 | ] 130 | assert repo.getClientByQuery(where: where) is repo.client 131 | 132 | -------------------------------------------------------------------------------- /spec/lib/loopback-repository.coffee: -------------------------------------------------------------------------------- 1 | 2 | { LoopbackRepository } = require('../base-domain-loopback') 3 | { LoopbackClient } = require('loopback-promised') 4 | 5 | 6 | describe 'LoopbackRepository', -> 7 | 8 | 9 | beforeEach -> 10 | 11 | @domain = require('../create-facade').create() 12 | 13 | class SampleModel extends @domain.constructor.Entity 14 | @properties: 15 | date: @TYPES.DATE 16 | 17 | class SampleModelRepository extends LoopbackRepository 18 | @modelName: 'sample-model' 19 | 20 | @domain.addClass('sample-model', SampleModel) 21 | @domain.addClass('sample-model-repository', SampleModelRepository) 22 | 23 | 24 | describe ',about class properties,', -> 25 | 26 | it 'has aclType, default is "admin"', -> 27 | assert LoopbackRepository.aclType is 'admin' 28 | 29 | it 'has empty lbModelName', -> 30 | assert LoopbackRepository.lbModelName is '' 31 | 32 | 33 | it 'has client, instance of LoopbackClient', -> 34 | repo = @domain.createRepository('sample-model') 35 | assert repo.client instanceof LoopbackClient 36 | assert repo.client.timeout is undefined 37 | 38 | 39 | it 'has client, instance of LoopbackClient customized with options', -> 40 | options = 41 | timeout: 1000 42 | debug: false 43 | accessToken: 'abc' 44 | 45 | repo = @domain.createRepository('sample-model', options) 46 | assert repo.client instanceof LoopbackClient 47 | assert repo.client.timeout is 1000 48 | assert repo.client.debug is false 49 | assert repo.client.accessToken is 'abc' 50 | 51 | 52 | describe 'modifyDate', -> 53 | it 'convert date properties to valid date format', -> 54 | data = 55 | date : '1986-03-10' 56 | @domain.createRepository('sample-model').modifyDate(data) 57 | assert data.date.match /1986-03-\d{2}T\d{2}:\d{2}:00\.000Z/ 58 | 59 | 60 | describe 'save', -> 61 | # save: (entity) -> 62 | # client = @getClientByEntity(entity) 63 | # @modifyDate(entity) 64 | # super(entity, client) 65 | describe 'get', -> 66 | # get: (id, foreignKey) -> 67 | # client = @getClientByForeignKey(foreignKey) 68 | # super(id, client) 69 | describe 'query', -> 70 | # query: (params) -> 71 | # client = @getClientByQuery(params) 72 | # super(params, client) 73 | describe 'singleQuery', -> 74 | # singleQuery: (params) -> 75 | # client = @getClientByQuery(params) 76 | # super(params, client) 77 | describe 'delete', -> 78 | # delete: (entity) -> 79 | # client = @getClientByEntity(entity) 80 | # super(entity, client) 81 | describe 'update', -> 82 | # update: (id, data) -> 83 | # client = @getClientByEntity(data) # FIXME fails if data doesnt contain foreign key 84 | # @modifyDate(data) 85 | # super(id, data, client) 86 | describe 'getClientByEntity', -> 87 | it 'returns @client', -> 88 | repo = @domain.createRepository('sample-model') 89 | assert repo.getClientByEntity() is repo.client 90 | 91 | 92 | describe 'getClientByForeignKey', -> 93 | it 'returns @client', -> 94 | repo = @domain.createRepository('sample-model') 95 | assert repo.getClientByForeignKey() is repo.client 96 | 97 | 98 | describe 'getClientByQuery', -> 99 | it 'returns @client', -> 100 | repo = @domain.createRepository('sample-model') 101 | assert repo.getClientByQuery() is repo.client 102 | 103 | 104 | describe 'parseSessionId', -> 105 | it 'split sessionId into accessToken and userId', -> 106 | repo = @domain.createRepository('sample-model') 107 | [ sessionId, userId ] = repo.parseSessionId('sessionId/userId') 108 | assert sessionId is 'sessionId' 109 | assert userId is 'userId' 110 | 111 | -------------------------------------------------------------------------------- /spec/lib/loopback-user-repository.coffee: -------------------------------------------------------------------------------- 1 | 2 | { LoopbackUserRepository, Entity } = require('../base-domain-loopback') 3 | { Promise, LoopbackUserClient } = require('loopback-promised') 4 | 5 | 6 | describe 'LoopbackUserClient', -> 7 | 8 | beforeEach -> 9 | 10 | @domain = require('../create-facade').create() 11 | 12 | class SampleModel extends Entity 13 | @properties: 14 | date: @TYPES.DATE 15 | 16 | class SampleModelRepository extends LoopbackUserRepository 17 | @modelName: 'sample-model' 18 | 19 | @domain.addClass('sample-model', SampleModel) 20 | @domain.addClass('sample-model-repository', SampleModelRepository) 21 | 22 | 23 | it 'has client, instance of LoopbackUserClient', -> 24 | repo = @domain.createRepository('sample-model') 25 | assert repo.client instanceof LoopbackUserClient 26 | 27 | it 'has client, instance of LoopbackUserClient with custom options', -> 28 | repo = @domain.createRepository('sample-model', timeout: 100) 29 | assert repo.client instanceof LoopbackUserClient 30 | assert repo.client.timeout is 100 31 | 32 | describe 'login', -> 33 | 34 | xit 'cannot login without email or password', -> 35 | 36 | xit 'cannot login with invalid email or password', -> 37 | 38 | xit 'logins with email and password', -> 39 | 40 | describe 'loginWithUsername', -> 41 | xit 'cannot login without username or password', -> 42 | 43 | xit 'cannot login with invalid username or password', -> 44 | 45 | xit 'logins with username and password', -> 46 | 47 | describe 'getBySessionId', -> 48 | xit 'cannot fetch a user model by invalid sessionId', -> 49 | 50 | xit 'fetchs a user model by valid sessionId', -> 51 | 52 | describe 'logout', -> 53 | xit 'succeeds even when sessionId is not valid', -> 54 | 55 | 56 | describe 'confirm', -> 57 | it 'returns boolean, depends on success of login, logout', -> 58 | repo = @domain.createRepository('sample-model') 59 | repo.login = -> Promise.resolve {} 60 | repo.logout = -> Promise.resolve {} 61 | repo.confirm().then (result) -> 62 | 63 | assert result 64 | 65 | repo.login = -> Promise.reject new Error() 66 | repo.confirm() 67 | 68 | .then (result) -> 69 | assert result is false 70 | -------------------------------------------------------------------------------- /spec/lib/model-definition.coffee: -------------------------------------------------------------------------------- 1 | 2 | ModelDefinition = require '../../src/lib/model-definition' 3 | Facade = require '../base-domain-loopback' 4 | 5 | { Entity, LoopbackRepository, LoopbackUserRepository, 6 | LoopbackRelationRepository, BaseModel, BaseRepository } = Facade 7 | 8 | describe 'ModelDefinition', -> 9 | 10 | beforeEach -> 11 | 12 | @domain = require('../create-facade').create() 13 | 14 | class Child extends Entity 15 | @belongsTo: 'pnt' 16 | @properties: pnt: @TYPES.MODEL 'parent' 17 | 18 | class Parent extends Entity 19 | class Member extends Entity 20 | 21 | class ChildRepository extends LoopbackRelationRepository 22 | @modelName: 'child' 23 | @lbModelName: 'cld' 24 | @aclType: 'public-read' 25 | 26 | class ParentRepository extends LoopbackRepository 27 | @modelName: 'parent' 28 | @lbModelName: 'loopback-parent' 29 | 30 | class NonEntity extends BaseModel 31 | class NonLoopback extends Entity 32 | class NonLoopbackRepository extends BaseRepository 33 | class MemberRepository extends LoopbackUserRepository 34 | 35 | @domain.addClass('child', Child) 36 | @domain.addClass('parent', Parent) 37 | @domain.addClass('member', Member) 38 | @domain.addClass('child-repository', ChildRepository) 39 | @domain.addClass('parent-repository', ParentRepository) 40 | @domain.addClass('non-entity', NonEntity) 41 | @domain.addClass('non-loopback', NonLoopback) 42 | @domain.addClass('non-loopback-repository', NonLoopbackRepository) 43 | @domain.addClass('member-repository', MemberRepository) 44 | 45 | @Child = Child 46 | @ChildRepository = ChildRepository 47 | @Member = Member 48 | @MemberRepository = MemberRepository 49 | 50 | 51 | describe 'constructor', -> 52 | 53 | it 'contains definition', -> 54 | def = new ModelDefinition(@Child, @ChildRepository, @domain) 55 | 56 | assert typeof def.definition is 'object' 57 | 58 | describe 'definition', -> 59 | before -> 60 | @defObj = new ModelDefinition(@Child, @ChildRepository, @domain).definition 61 | 62 | it 'contains aclType, the same as repository\'s aclType', -> 63 | assert @defObj.aclType is 'public-read' 64 | 65 | it 'contains name, the same as LoopbackRepository.getLbModelName()', -> 66 | assert @defObj.name is 'cld' 67 | 68 | it 'contains plural name, the same as "name"', -> 69 | assert @defObj.plural is 'cld' 70 | 71 | it 'contains base = PersistedModel', -> 72 | assert @defObj.base is 'PersistedModel' 73 | 74 | it 'contains idInjection = true', -> 75 | assert @defObj.idInjection 76 | 77 | it 'contains properties', -> 78 | assert @defObj.properties? 79 | 80 | it 'contains validations', -> 81 | assert @defObj.validations? 82 | 83 | it 'contains [belongsTo] relations', -> 84 | assert @defObj.relations? 85 | assert @defObj.relations.pnt? 86 | assert @defObj.relations.pnt.type is 'belongsTo' 87 | 88 | 89 | describe 'getName', -> 90 | 91 | it 'returns name of the entity model', -> 92 | def = new ModelDefinition(@Child, @ChildRepository, @domain) 93 | assert def.getName() is 'cld' 94 | 95 | 96 | describe 'getPluralName', -> 97 | 98 | it 'returns plural name of the entity model', -> 99 | def = new ModelDefinition(@Child, @ChildRepository, @domain) 100 | assert def.getPluralName() is 'cld' 101 | 102 | describe 'getBase', -> 103 | 104 | it 'returns "PersistedModel" if entity model isnt child class of LoopbackUserRepository', -> 105 | def = new ModelDefinition(@Child, @ChildRepository, @domain) 106 | assert def.getBase() is 'PersistedModel' 107 | 108 | 109 | it 'returns "User" if entity model isnt child class of LoopbackUserRepository', -> 110 | def = new ModelDefinition(@Member, @MemberRepository, @domain) 111 | assert def.getBase() is 'User' 112 | 113 | describe 'export', -> 114 | 115 | it 'returns definition object', -> 116 | def = new ModelDefinition(@Child, @ChildRepository, @domain) 117 | assert def.export() is def.definition 118 | 119 | 120 | describe 'getEntityProps', -> 121 | 122 | it 'returns typeInfo of the sub-entities', -> 123 | def = new ModelDefinition(@Child, @ChildRepository, @domain) 124 | assert def.getEntityProps().pnt? 125 | assert def.getEntityProps().pnt.model is 'parent' 126 | 127 | 128 | describe 'getBelongsToRelations', -> 129 | 130 | it 'returns "belongsTo" relations', -> 131 | 132 | rels = new ModelDefinition(@Child, @ChildRepository, @domain).getBelongsToRelations() 133 | 134 | assert rels.pnt? 135 | assert rels.pnt.type is 'belongsTo' 136 | assert rels.pnt.model is 'loopback-parent' 137 | assert rels.pnt.foreignKey is 'parentId' 138 | 139 | 140 | describe 'setHasManyRelation', -> 141 | 142 | it 'set "hasMany" relations to definition object', -> 143 | 144 | def = new ModelDefinition(@Child, @ChildRepository, @domain) 145 | def.setHasManyRelation('xxx', 'xxxId') 146 | 147 | rels = def.definition.relations 148 | 149 | assert rels.xxx? 150 | assert rels.xxx.type is 'hasMany' 151 | assert rels.xxx.model is 'xxx' 152 | assert rels.xxx.foreignKey is 'xxxId' 153 | 154 | 155 | 156 | module.exports = ModelDefinition 157 | -------------------------------------------------------------------------------- /spec/lib/setting-exporter.coffee: -------------------------------------------------------------------------------- 1 | 2 | 3 | SettingExporter = require '../../src/lib/setting-exporter' 4 | Facade = require '../base-domain-loopback' 5 | 6 | describe 'SettingExporter', -> 7 | 8 | beforeEach -> 9 | 10 | @domain = require('../create-facade').create(dirname: __dirname + '/../domains/music-live') 11 | @loopbackDefinitions = new SettingExporter(@domain).export() 12 | @modelDefinitions = @loopbackDefinitions.models 13 | 14 | 15 | describe 'export', -> 16 | 17 | it 'export entities', -> 18 | assert typeof @modelDefinitions.song is 'object' 19 | assert typeof @modelDefinitions.player is 'object' 20 | assert typeof @modelDefinitions.instrument is 'object' 21 | 22 | it 'does not export entity with no repository', -> 23 | assert not @modelDefinitions.staff? 24 | 25 | it 'does not export non entity models', -> 26 | assert not @modelDefinitions['live-info']? 27 | 28 | 29 | it 'appends "hasMany" relations', -> 30 | playerDefObj = @modelDefinitions.player 31 | assert playerDefObj.relations? 32 | assert playerDefObj.relations.song? 33 | assert playerDefObj.relations.song.type is 'hasMany' 34 | 35 | it 'does not append "hasMany" relations to non-has-many relations', -> 36 | for entityName in ['instrument', 'song'] 37 | rels = @modelDefinitions[entityName].relations 38 | for relProp, relInfo of rels 39 | assert.notEqual relInfo.type, 'hasMany' 40 | assert relInfo.type is 'belongsTo' 41 | 42 | context 'medical', -> 43 | beforeEach -> 44 | @domain = require('../create-facade').create(dirname: __dirname + '/../domains/medical') 45 | @loopbackDefinitions = new SettingExporter(@domain).export() 46 | @modelDefinitions = @loopbackDefinitions.models 47 | 48 | it 'does not append "hasMany" relations to a model that is out of aggregate', -> 49 | assert Object.keys(@modelDefinitions.hospital.relations).length is 0 50 | 51 | 52 | 53 | describe 'getAllEntityModels', -> 54 | 55 | it 'returns all entity models registered in the domain', -> 56 | classes = new SettingExporter(@domain).getAllEntityModels() 57 | 58 | assert classes.length is 4 59 | 60 | for klass in classes 61 | assert(klass::) instanceof Facade.Entity 62 | assert klass.isEntity 63 | 64 | 65 | describe 'loadAll', -> 66 | 67 | it 'does nothing when there is no domain dir', -> 68 | 69 | d = require('../create-facade').create(dirname: __dirname + '/../domains/xxx-no-dir') 70 | 71 | assert not require('fs').existsSync(d.dirname) 72 | 73 | assert Object.keys d.classes.length is 0 74 | 75 | new SettingExporter(d).loadAll() 76 | 77 | assert Object.keys d.classes.length is 0 78 | 79 | 80 | 81 | it 'requires entity models in the domain dir', -> 82 | d = require('../create-facade').create(dirname: __dirname + '/../domains/music-live') 83 | assert Object.keys d.classes.length is 0 84 | new SettingExporter(d).loadAll() 85 | assert Object.keys d.classes.length is 13 86 | 87 | 88 | module.exports = SettingExporter 89 | -------------------------------------------------------------------------------- /spec/system-test.coffee: -------------------------------------------------------------------------------- 1 | 2 | Facade = require './base-domain-loopback' 3 | 4 | domain = Facade.createInstance dirname: __dirname + '/domains/music-live', debug: false 5 | 6 | modelDefinitions = domain.getModelDefinitions() 7 | 8 | before -> 9 | 10 | @timeout 10000 11 | 12 | require('loopback-with-admin').run(modelDefinitions).then (lbInfo) -> 13 | 14 | domain.setBaseURL lbInfo.getURL() 15 | domain.setSessionId lbInfo.getAdminTokens()[0] 16 | 17 | 18 | 19 | describe 'domain', -> 20 | 21 | it 'can access to loopback', -> 22 | 23 | playerObj = 24 | id: 'shin' 25 | name: 'shin' 26 | email: 'shinout@shinout.com' 27 | password: 'shinout' 28 | 29 | domain.createRepository('player').save(playerObj).then (player) -> 30 | songObj = 31 | id: 'lowdown' 32 | name: 'lowdown' 33 | author: player 34 | domain.createRepository('song').save(songObj) 35 | 36 | .then (song) -> 37 | assert song instanceof domain.getModel 'song' 38 | 39 | 40 | it 'can get 1:N-related object', -> 41 | 42 | domain.createRepository('song').query(where: authorId: 'shin').then (songs) -> 43 | assert songs.length is 1 44 | 45 | 46 | it 'can get N:1-related object', -> 47 | 48 | domain.createRepository('song').singleQuery(where: {id: 'lowdown'}, include: 'author').then (song) -> 49 | assert song.author instanceof domain.getModel 'player' 50 | 51 | it 'can login', -> 52 | domain.createRepository('player').login('shinout@shinout.com', 'shinout', 'include').then (result) => 53 | @sessionId = result.sessionId 54 | assert @sessionId.match /\/shin$/ 55 | assert result.ttl is modelDefinitions.models.player.ttl 56 | 57 | it 'can access to "owner" aclType models', -> 58 | 59 | domain.setSessionId @sessionId 60 | 61 | domain.createRepository('song').query(where: authorId: 'shin').then (songs) -> 62 | assert songs.length is 1 63 | 64 | 65 | -------------------------------------------------------------------------------- /src/lib/loopback-domain-facade.coffee: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | LoopbackPromised = require 'loopback-promised' 4 | Facade = require 'base-domain' 5 | 6 | ###* 7 | @class LoopbackDomainFacade 8 | @extends Facade 9 | @module base-domain-loopback 10 | ### 11 | class LoopbackDomainFacade extends Facade 12 | 13 | ###* 14 | constructor 15 | 16 | @param {Object} [options] 17 | @param {String} options.baseURL loopback api root 18 | @param {String} options.sessionId 19 | @param {Boolean} options.debug 20 | ### 21 | constructor: (options = {}) -> 22 | 23 | super(options) 24 | 25 | @debug = !!options.debug 26 | 27 | @lbPromised = LoopbackPromised.createInstance 28 | baseURL: options.baseURL 29 | 30 | @sessionId = options.sessionId 31 | 32 | @timeout = options.timeout 33 | 34 | 35 | ###* 36 | set sessionId. Repositories generated after setSessionId(newSessionIDs) use the new sessionId 37 | 38 | @method setSessionId 39 | @param {String} sessionId 40 | ### 41 | setSessionId: (@sessionId) -> 42 | 43 | 44 | 45 | ###* 46 | set baseURL. Repositories generated after setBaseURL(newBaseURL) use the new baseURL 47 | 48 | @method setBaseURL 49 | @param {String} baseURL 50 | ### 51 | setBaseURL: (baseURL) -> 52 | @lbPromised.baseURL = baseURL 53 | return 54 | 55 | 56 | ###* 57 | Get model definition objects, which [loopback-with-admin](https://github.com/CureApp/loopback-with-admin)))) 58 | 59 | @method getModelDefinitions 60 | @return {Object} 61 | ### 62 | getModelDefinitions: -> 63 | new @constructor.SettingExporter(@).export() 64 | 65 | 66 | @LoopbackRepository : require './loopback-repository' 67 | @LoopbackUserRepository : require './loopback-user-repository' 68 | @LoopbackRelationRepository : require './loopback-relation-repository' 69 | 70 | 71 | module.exports = LoopbackDomainFacade 72 | -------------------------------------------------------------------------------- /src/lib/loopback-relation-repository.coffee: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | LoopbackRepository = require './loopback-repository' 4 | ###* 5 | @class LoopbackUserRepository 6 | @extends LoopbackRepository 7 | @module base-domain-loopback 8 | ### 9 | class LoopbackRelationRepository extends LoopbackRepository 10 | 11 | ###* 12 | prop name this model belongs to 13 | 14 | @property belongsTo 15 | @protected 16 | @type String 17 | ### 18 | @belongsTo: null 19 | 20 | 21 | ###* 22 | constructor 23 | 24 | @constructor 25 | @param {Object} [options] 26 | @param {any} [options.id] the id of the "belongsTo" model 27 | @param {String} [options.sessionId] Session ID 28 | @param {Boolean} [options.debug] shows debug log if true 29 | ### 30 | constructor: (options = {}, root) -> 31 | if not @constructor.belongsTo 32 | throw new Error """ 33 | You must set @belongsTo and @foreignKeyName when extending RelationRepository. 34 | """ 35 | 36 | super(options, root) 37 | 38 | modelProps = @facade.getModelProps(@getModelName()) 39 | belongsTo = @constructor.belongsTo 40 | subModelName = modelProps.getSubModelName(belongsTo) 41 | subIdName = modelProps.getIdPropByEntityProp(belongsTo) 42 | 43 | if not modelProps.isEntity belongsTo 44 | throw new Error """ 45 | "belongsTo" property: #{belongsTo} is not an entity prop. 46 | """ 47 | 48 | # Checking if model has multiple same submodels. If so, relation name will include foreignKey. 49 | hasSameSubModel = do => 50 | for prop in modelProps.getEntityProps() when prop isnt belongsTo 51 | return true if modelProps.getSubModelName(prop) is subModelName 52 | return false 53 | 54 | @foreignKeyName = subIdName 55 | 56 | @relClient = @getRelatedClient 57 | model : subModelName 58 | foreignKey : if hasSameSubModel then @foreignKeyName else null 59 | 60 | 61 | ###* 62 | get client by entity 63 | if entity has foreign key, relClient is returned. 64 | 65 | @method getClientByEntity 66 | @protected 67 | @param {Entity|Object} entity 68 | @return {LoopbackClient} client 69 | ### 70 | getClientByEntity: (entity) -> 71 | foreignKey = entity?[@foreignKeyName] 72 | @getClientByForeignKey(foreignKey) 73 | 74 | 75 | ###* 76 | get client by foreignKey 77 | 78 | @method getClientByForeignKey 79 | @protected 80 | @param {String} foreignKey 81 | @return {LoopbackClient} client 82 | ### 83 | getClientByForeignKey: (foreignKey) -> 84 | if foreignKey? 85 | @relClient.setId foreignKey 86 | return @relClient 87 | else 88 | return @client 89 | 90 | 91 | ###* 92 | get client by query 93 | 94 | @method getClientByQuery 95 | @protected 96 | @param {Object} query 97 | @param {String} [query.foreignKey] 98 | @return {LoopbackClient} client 99 | ### 100 | getClientByQuery: (query = {}) -> 101 | if query.hasOwnProperty('foreignKey') 102 | foreignKey = query.foreignKey 103 | else 104 | foreignKey = query.where?[@foreignKeyName] 105 | 106 | if typeof foreignKey isnt 'object' 107 | @getClientByForeignKey(foreignKey) 108 | else 109 | @client 110 | 111 | 112 | 113 | module.exports = LoopbackRelationRepository 114 | -------------------------------------------------------------------------------- /src/lib/loopback-repository.coffee: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | { BaseAsyncRepository, Entity } = require('base-domain') 4 | 5 | moment = require 'moment' 6 | relationName = require './relation-name' 7 | 8 | ###* 9 | @class LoopbackRepository 10 | @extends BaseAsyncRepository 11 | @module base-domain-loopback 12 | ### 13 | class LoopbackRepository extends BaseAsyncRepository 14 | 15 | ###* 16 | aclType : type of access control list in [loopback-with-admin](https://github.com/cureapp/loopback-with-admin) 17 | ### 18 | @aclType: 'admin' 19 | 20 | 21 | ###* 22 | model name used in Loopback 23 | it will be the same value as @modelName if not set 24 | 25 | @property lbModelName 26 | @static 27 | @type String 28 | ### 29 | @lbModelName: '' 30 | 31 | 32 | 33 | ###* 34 | Map to convert loopback's object prop into model prop 35 | 36 | key: loopback's prop 37 | value: model prop 38 | 39 | if value is null or undefined, the property only exists in loopback and is removed from the created model. 40 | 41 | @property {Object} props 42 | @static 43 | ### 44 | @props: null 45 | 46 | 47 | ###* 48 | constructor 49 | 50 | @constructor 51 | @param {Object} [options] 52 | @param {String} [options.sessionId] Session ID 53 | @param {Boolean} [options.debug] shows debug log if true 54 | @params {RootInterface} root 55 | ### 56 | constructor: (options = {}, root) -> 57 | super(root) 58 | 59 | facade = @facade 60 | 61 | 62 | lbModelName = @constructor.getLbModelName() 63 | 64 | sessionId = options.sessionId or facade.sessionId 65 | 66 | [accessToken, userId] = @parseSessionId sessionId 67 | 68 | options.accessToken ?= accessToken 69 | options.debug ?= facade.debug 70 | options.timeout ?= facade.timeout 71 | 72 | @client = facade.lbPromised.createClient(lbModelName, options) 73 | 74 | @relClients = {} 75 | 76 | 77 | ###* 78 | get model name used in LoopBack 79 | @method getLbModelName 80 | @static 81 | @return {String} 82 | ### 83 | @getLbModelName: -> 84 | @lbModelName or @modelName 85 | 86 | 87 | 88 | ###* 89 | Create model instance from result from client 90 | 91 | @method createFromResult 92 | @protected 93 | @param {Object} obj 94 | @param {Object} [options] 95 | @return {BaseModel} model 96 | ### 97 | createFromResult: (obj, options) -> 98 | 99 | return super if not obj? 100 | 101 | for lbProp, prop of @constructor.props ? {} 102 | if prop? 103 | obj[prop] = obj[lbProp] 104 | 105 | delete obj[lbProp] 106 | 107 | super(obj, options) 108 | 109 | 110 | ###* 111 | convert 'date' type property for loopback format 112 | 113 | @method modifyDate 114 | @private 115 | @param {Entity|Object} data 116 | ### 117 | modifyDate: (data) -> 118 | modelProps = @facade.getModelProps(@getModelName()) 119 | for dateProp in modelProps.dates 120 | val = data[dateProp] 121 | if val? 122 | data[dateProp] = moment(val).toISOString() 123 | return 124 | 125 | 126 | ###* 127 | Update or insert a model instance 128 | 129 | @method save 130 | @public 131 | @param {Entity|Object} entity 132 | @param {Object} [options] 133 | @return {Promise(Entity)} entity (the same instance from input, if entity given,) 134 | ### 135 | save: (entity, options = {}) -> 136 | if not options.client and options.relation 137 | options.client = @getRelatedClient(options.relation) 138 | else 139 | options.client ?= @getClientByEntity(entity) 140 | 141 | @modifyDate(entity) 142 | super(entity, options) 143 | 144 | 145 | ###* 146 | get entity by id. 147 | 148 | @method get 149 | @public 150 | @param {String|Number} id 151 | @param {Object} [options] 152 | @param {String} [options.foreignKey] 153 | @return {Promise(Entity)} entity 154 | ### 155 | get: (id, options = {}) -> 156 | if not options.client and options.relation 157 | options.client = @getRelatedClient(options.relation) 158 | else 159 | options.client ?= @getClientByForeignKey(options.foreignKey) 160 | super(id, options) 161 | 162 | 163 | ###* 164 | get entities by id. 165 | 166 | @method getByIds 167 | @public 168 | @param {Array|(String|Number)} ids 169 | @param {Object} [options] 170 | @return {Promise(Array(Entity))} entities 171 | ### 172 | getByIds: (ids, options) -> 173 | @query(where: { id: inq: ids }, options) 174 | 175 | 176 | ###* 177 | Find all model instances that match params 178 | 179 | @method query 180 | @public 181 | @param {Object} [params] query parameters 182 | @param {Object} [options] 183 | @return {Promise(Array(Entity))} array of entities 184 | ### 185 | query: (params = {}, options = {}) -> 186 | 187 | if params.relation and not options.relation 188 | options.relation = params.relation 189 | 190 | if not options.client and options.relation 191 | options.client = @getRelatedClient(options.relation) 192 | else 193 | options.client ?= @getClientByQuery(params) 194 | 195 | super(params, options) 196 | 197 | 198 | ###* 199 | Find one model instance that matches params, Same as query, but limited to one result 200 | 201 | @method singleQuery 202 | @public 203 | @param {Object} [params] query parameters 204 | @param {Object} [options] 205 | @return {Promise(Entity)} entity 206 | ### 207 | singleQuery: (params, options = {}) -> 208 | if not options.client and options.relation 209 | options.client = @getRelatedClient(options.relation) 210 | else 211 | options.client ?= @getClientByQuery(params) 212 | super(params, options) 213 | 214 | 215 | 216 | ###* 217 | Destroy the given entity (which must have "id" value) 218 | 219 | @method delete 220 | @public 221 | @param {Entity} entity 222 | @param {Object} [options] 223 | @return {Promise(Boolean)} isDeleted 224 | ### 225 | delete: (entity, options = {}) -> 226 | if not options.client and options.relation 227 | options.client = @getRelatedClient(options.relation) 228 | else 229 | options.client ?= @getClientByEntity(entity) 230 | super(entity, options) 231 | 232 | 233 | ###* 234 | Update set of attributes. 235 | 236 | @method update 237 | @public 238 | @param {any} id id of the entity to update 239 | @param {Object} data key-value pair to update 240 | @param {Object} [options] 241 | @return {Promise(Entity)} updated entity 242 | ### 243 | update: (id, data, options = {}) -> 244 | if not options.client and options.relation 245 | options.client = @getRelatedClient(options.relation) 246 | else 247 | options.client ?= @getClientByEntity(data) # FIXME fails if data doesnt contain foreign key 248 | @modifyDate(data) 249 | super(id, data, options) 250 | 251 | 252 | ###* 253 | Update set of attributes and returns newly-updated props (other than `props`) 254 | 255 | @method updateProps 256 | @public 257 | @param {Entity} entity 258 | @param {Object} data key-value pair to update (notice: this must not be instance of Entity) 259 | @param {Object} [options] 260 | @param {ResourceClientInterface} [options.client=@client] 261 | @return {Object} updated props 262 | ### 263 | updateProps: (entity, props = {}, options = {}) -> 264 | if not options.client and options.relation 265 | options.client = @getRelatedClient(options.relation) 266 | else 267 | options.client ?= @getClientByEntity(props) # FIXME fails if props don't contain foreign key 268 | @modifyDate(props) 269 | super(entity, props, options) 270 | 271 | 272 | 273 | ###* 274 | Return the number of models that match the optional "where" filter. 275 | 276 | @method count 277 | @public 278 | @param {Object} [where] 279 | @return {Promise(Number)} 280 | ### 281 | count: (where = {}, options = {}) -> 282 | 283 | if options.client 284 | { client } = options 285 | else if options.relation 286 | client = @getRelatedClient(options.relation) 287 | else 288 | client ?= @getClientByQuery(where: where) 289 | 290 | client.count(where) 291 | 292 | 293 | ###* 294 | Get loopback-related-client 295 | @method getRelatedClient 296 | @protected 297 | @param {Object} params 298 | @param {String} params.modelName foreign model name 299 | @param {String} params.id foreign id 300 | @param {String} [params.relation] relation name 301 | @param {String} [params.foreignKey] foreign key prop. 302 | @param {String} [params.through] 303 | @param {String} [params.keyThrough] 304 | @return {LoopbackRelatedClient} 305 | ### 306 | getRelatedClient: (params = {}) -> 307 | 308 | { model, name, id, foreignKey, through, keyThrough } = params 309 | 310 | return null if not model 311 | 312 | relName = name ? relationName 313 | model : @constructor.getLbModelName() 314 | foreignKey : foreignKey 315 | through : through 316 | keyThrough : keyThrough 317 | 318 | clientKey = model + '.' + relName 319 | 320 | if client = @relClients[clientKey] 321 | client.setId id 322 | return client 323 | 324 | try 325 | Repo = @facade.require(model + '-repository') 326 | throw new Error() if (Repo::) not instanceof LoopbackRepository 327 | catch e 328 | console.error(""" 329 | Error in LoopbackRepository#getRelatedClient(). '#{model}-repository' is not found, 330 | or it is not an instance of LoopbackRepository. 331 | model name must be compatible with LoopbackRepository when querying with relation. 332 | """) 333 | return null 334 | 335 | 336 | relClientOptions = 337 | one : Repo.getLbModelName() 338 | many : relName 339 | id : id 340 | accessToken : @client.accessToken 341 | timeout : @client.timeout 342 | debug : @client.debug 343 | 344 | @relClients[clientKey] = @facade.lbPromised.createRelatedClient(relClientOptions) 345 | 346 | 347 | 348 | ###* 349 | get client by entity. By default it returns @client 350 | 351 | @method getClientByEntity 352 | @protected 353 | @param {Entity|Object} entity 354 | @return {LoopbackClient} client 355 | ### 356 | getClientByEntity: (entity) -> 357 | return @client 358 | 359 | 360 | ###* 361 | get client by foreign key. By default it returns @client 362 | 363 | @method getClientByForeignKey 364 | @protected 365 | @param {String} foreignKey 366 | @return {LoopbackClient} client 367 | ### 368 | getClientByForeignKey: (foreignKey) -> 369 | return @client 370 | 371 | 372 | ###* 373 | get client by query value. By default it returns @client 374 | 375 | @method getClientByQuery 376 | @protected 377 | @param {Object} query 378 | @return {LoopbackClient} client 379 | ### 380 | getClientByQuery: (query) -> 381 | return @client 382 | 383 | 384 | ###* 385 | get accessToken and userId by sessionId 386 | 387 | @method parseSessionId 388 | @protected 389 | @param {String} sessionId 390 | @return {Array(String)} [accessToken, userId] 391 | ### 392 | parseSessionId: (sessionId) -> 393 | if not sessionId 394 | return [null, null] 395 | return sessionId.split('/') 396 | 397 | 398 | 399 | module.exports = LoopbackRepository 400 | -------------------------------------------------------------------------------- /src/lib/loopback-user-repository.coffee: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | LoopbackRepository = require './loopback-repository' 4 | 5 | ###* 6 | @class LoopbackUserRepository 7 | @extends LoopbackRepository 8 | @module base-domain-loopback 9 | ### 10 | class LoopbackUserRepository extends LoopbackRepository 11 | ###* 12 | constructor 13 | 14 | @constructor 15 | @param {Object} [options] 16 | @param {String} [options.sessionId] Session ID 17 | @param {Boolean} [options.debug] shows debug log if true 18 | ### 19 | constructor: (options = {}, root) -> 20 | super(options, root) 21 | modelName = @constructor.modelName 22 | @client = @facade.lbPromised.createUserClient(modelName, options) 23 | 24 | 25 | 26 | ###* 27 | get sessionId from account information (email/password) 28 | 29 | @param {String} email 30 | @param {String} password 31 | @param {Boolean|String} [include] fetch related model if true. fetch submodels if 'include'. 32 | @return {Promise(Object)} 33 | ### 34 | login: (email, password, include) -> 35 | facade = @facade 36 | 37 | includeUser = include? 38 | @client.login({email: email, password: password}, if includeUser then 'user' else null).then (response) => 39 | accessToken = response.id 40 | userId = if includeUser then response.user.id else response.userId 41 | 42 | ret = 43 | sessionId: accessToken + '/' + userId 44 | ttl: response.ttl 45 | 46 | if includeUser 47 | model = @factory.createFromObject(response.user) 48 | ret[@constructor.modelName] = model 49 | ret.user = model 50 | 51 | if include is 'include' 52 | oldSessionId = facade.sessionId 53 | facade.setSessionId ret.sessionId 54 | 55 | return model.$include(accessToken: accessToken).then (newModel) => 56 | ret[@constructor.modelName] = newModel 57 | ret.user = newModel 58 | facade.setSessionId oldSessionId 59 | return ret 60 | 61 | else 62 | ret[@constructor.modelName] = model 63 | ret.user = model 64 | return ret 65 | 66 | else 67 | return ret 68 | 69 | ###* 70 | get sessionId from account information (username/password) 71 | 72 | @param {String} username 73 | @param {String} password 74 | @param {Boolean|String} [include] fetch related model if true. fetch submodels if 'include'. 75 | @return {Promise(Object)} 76 | ### 77 | loginWithUsername: (username, password, include) -> 78 | facade = @facade 79 | 80 | includeUser = include? 81 | @client.login({username: username, password: password}, if includeUser then 'user' else null).then (response) => 82 | accessToken = response.id 83 | userId = if includeUser then response.user.id else response.userId 84 | 85 | ret = 86 | sessionId: accessToken + '/' + userId 87 | ttl: response.ttl 88 | 89 | if includeUser 90 | model = @factory.createFromObject(response.user) 91 | ret[@constructor.modelName] = model 92 | ret.user = model 93 | 94 | if include is 'include' 95 | oldSessionId = facade.sessionId 96 | facade.setSessionId ret.sessionId 97 | 98 | return model.$include(accessToken: accessToken).then (newModel) => 99 | ret[@constructor.modelName] = newModel 100 | ret.user = newModel 101 | facade.setSessionId oldSessionId 102 | return ret 103 | 104 | else 105 | ret[@constructor.modelName] = model 106 | ret.user = model 107 | return ret 108 | 109 | else 110 | return ret 111 | 112 | ###* 113 | logout (delete session) 114 | 115 | @param {String} sessionId 116 | @return {Promise} 117 | ### 118 | logout: (sessionId) -> 119 | [accessToken, userId] = @parseSessionId sessionId 120 | client = @facade.lbPromised.createUserClient(@constructor.modelName, 121 | debug: @client.debug 122 | accessToken: accessToken 123 | ) 124 | 125 | client.logout(accessToken) 126 | 127 | 128 | ###* 129 | get user model by sessionId 130 | 131 | @method getBySessionId 132 | @param {String} sessionId 133 | @param {Object} [options] 134 | @param {Boolean|String} [options.include] include related models or not. 135 | @return {Promise(Entity)} 136 | ### 137 | getBySessionId: (sessionId, options = {}) -> 138 | [accessToken, userId] = @parseSessionId sessionId 139 | client = @facade.lbPromised.createUserClient(@constructor.modelName, 140 | debug: @client.debug 141 | accessToken: accessToken 142 | ) 143 | 144 | client.findById(userId).then (user) => 145 | model = @factory.createFromObject user 146 | if options.include 147 | facade = @facade 148 | oldSessionId = facade.sessionId 149 | facade.setSessionId sessionId 150 | 151 | return model.include().then -> 152 | facade.setSessionId oldSessionId 153 | return model 154 | 155 | else 156 | return model 157 | 158 | .catch (e) -> 159 | 160 | if e.isLoopbackResponseError 161 | return null 162 | 163 | throw e 164 | 165 | 166 | ###* 167 | confirm existence of account by email and password 168 | 169 | @param {String} email 170 | @param {String} password 171 | @return {Promise(Boolean)} existence of the account 172 | ### 173 | confirm: (email, password) -> 174 | @login(email, password).then (result) => 175 | @logout(result.sessionId).then -> 176 | return true 177 | .catch (e) -> 178 | return false 179 | 180 | 181 | ###* 182 | Override original method. 183 | Enable to preserve password property using `__password` option. 184 | Mainly for immutable entities. 185 | ### 186 | createFromResult: (obj, options = {}) -> 187 | return super if not options.__password? 188 | obj.password = options.__password 189 | return super(obj, options) 190 | 191 | 192 | ###* 193 | Update or insert a model instance 194 | reserves password property, as loopback does not return password 195 | 196 | @method save 197 | @public 198 | @param {Entity|Object} entity 199 | @return {Promise(Entity)} entity (the same instance from input, if entity given,) 200 | ### 201 | save: (entity, options = {}) -> 202 | 203 | options.__password = entity?.password 204 | 205 | super(entity, options) 206 | 207 | 208 | module.exports = LoopbackUserRepository 209 | -------------------------------------------------------------------------------- /src/lib/model-definition.coffee: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | LoopbackRepository = require './loopback-repository' 4 | LoopbackUserRepository = require './loopback-user-repository' 5 | 6 | relationName = require './relation-name' 7 | 8 | ###* 9 | loopback model definition of one entity 10 | 11 | @class ModelDefinition 12 | @module base-domain-loopback 13 | ### 14 | class ModelDefinition 15 | 16 | constructor: (@EntityModel, @LoopbackRepository, @facade) -> 17 | 18 | @definition = 19 | aclType : @LoopbackRepository.aclType 20 | name : @getName() 21 | plural : @getPluralName() 22 | base : @getBase() 23 | idInjection : true 24 | properties : {} 25 | validations : [] 26 | relations : @getBelongsToRelations() 27 | 28 | for k, v of @LoopbackRepository.lbDefinitions ? {} 29 | @definition[k] = v 30 | 31 | 32 | ###* 33 | get model name 34 | 35 | @method getName 36 | @public 37 | @return {String} lbModelName 38 | ### 39 | getName: -> 40 | @LoopbackRepository.getLbModelName() 41 | 42 | 43 | ###* 44 | get plural model name: the same as getName() for simplicity 45 | 46 | @method getPluralName 47 | @private 48 | @return {String} lbModelName 49 | ### 50 | getPluralName: -> 51 | @LoopbackRepository.getLbModelName() 52 | 53 | 54 | ###* 55 | get "base" setting. 56 | "User" or "PersistedModel" 57 | 58 | @method getName 59 | @public 60 | @return {String} 61 | ### 62 | getBase: -> 63 | if (@LoopbackRepository::) instanceof LoopbackUserRepository 64 | return 'User' 65 | else 66 | return 'PersistedModel' 67 | 68 | 69 | ###* 70 | Returns the definition 71 | 72 | @method export 73 | @public 74 | @return {Object} definition 75 | ### 76 | export: -> 77 | @definition 78 | 79 | 80 | ###* 81 | get props info of sub-entities 82 | 83 | @method getEntityProps 84 | @return {Object(TypeInfo)} 85 | ### 86 | getEntityProps: -> 87 | info = {} 88 | modelProps = @facade.getModelProps(@EntityModel.getName()) 89 | 90 | for prop in modelProps.getEntityProps() 91 | info[prop] = modelProps.typeInfoDic[prop] # TODO: modelProps.typeInfoDic should be private. 92 | 93 | return info 94 | 95 | 96 | ###* 97 | get "belongsTo" relations 98 | 99 | @private 100 | ### 101 | getBelongsToRelations: -> 102 | rels = {} 103 | for prop, typeInfo of @getEntityProps() 104 | 105 | relModelProps = @facade.getModelProps(typeInfo.model) 106 | 107 | try 108 | Repo = @facade.require(typeInfo.model + '-repository') 109 | continue if (Repo::) not instanceof LoopbackRepository 110 | catch e 111 | continue # skip if repository is not found 112 | 113 | relLbModelName = Repo.getLbModelName() 114 | 115 | rels[prop] = 116 | type : 'belongsTo' 117 | model : relLbModelName 118 | foreignKey : typeInfo.idPropName 119 | 120 | return rels 121 | 122 | 123 | ###* 124 | set "hasMany" relations 125 | 126 | @method setHasManyRelation 127 | @param {String} relLbModelName 128 | @param {String} idPropName foreignKey 129 | ### 130 | setHasManyRelation: (relLbModelName, idPropName) -> 131 | rel = 132 | type : 'hasMany' 133 | model : relLbModelName 134 | foreignKey : idPropName 135 | 136 | relName = relationName(rel) 137 | @definition.relations[relName] = rel 138 | 139 | @definition.relations[relLbModelName] = rel # for backward compatibility 140 | 141 | ###* 142 | set "hasManyThrough" relations 143 | 144 | @method setHasManyThroughRelation 145 | @param {String} relLbModelName 146 | @param {String} idPropName foreignKey 147 | ### 148 | setHasManyThroughRelation: (params = {}) -> 149 | 150 | relName = relationName(params) 151 | 152 | { model, foreignKey, keyThrough, through } = params 153 | 154 | @definition.relations[relName] = 155 | type : 'hasMany' 156 | model : model 157 | foreignKey : foreignKey 158 | keyThrough : keyThrough 159 | through : through 160 | 161 | 162 | 163 | addCustomRelations: -> 164 | 165 | return if not @LoopbackRepository.relations? 166 | 167 | for relName, params of @LoopbackRepository.relations 168 | 169 | @definition.relations[relName] = params 170 | 171 | 172 | 173 | module.exports = ModelDefinition 174 | -------------------------------------------------------------------------------- /src/lib/relation-name.coffee: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = (params = {}) -> 4 | 5 | { model, foreignKey, keyThrough, through } = params 6 | 7 | if through 8 | return model + '-via-' + keyThrough + '-at-' + through + '-with-' + foreignKey 9 | 10 | else if foreignKey 11 | return model + '-with-' + foreignKey 12 | 13 | else 14 | return model 15 | 16 | -------------------------------------------------------------------------------- /src/lib/setting-exporter.coffee: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | debug = require('debug')('base-domain-loopback:setting-exporter') 4 | 5 | fs = require 'fs' 6 | LoopbackRepository = require './loopback-repository' 7 | ModelDefinition = require './model-definition' 8 | 9 | ###* 10 | export model info into loopback-with-admin's format (loopback-with-admin >=v1.8.0) 11 | only available in Node.js 12 | 13 | @class SettingExporter 14 | @module base-domain-loopback 15 | ### 16 | class SettingExporter 17 | 18 | constructor: (@facade) -> 19 | 20 | 21 | ###* 22 | Create loopback definition setting object 23 | @method export 24 | @public 25 | @return {Object} 26 | ### 27 | export: -> 28 | models: @createModelDefinitions() 29 | customRoles: @createCustomRoleDefinitions() 30 | 31 | 32 | ###* 33 | Create definitions of custom roles (for ACL). 34 | 35 | Prepare directory containing js files exporting a function to pass to Role.registerResolver(name, fn) 36 | https://docs.strongloop.com/display/public/LB/Defining+and+using+roles 37 | http://apidocs.strongloop.com/loopback/#role-registerresolver 38 | 39 | Define the directory as Facade#customRolePath or move the directory to Facade#dirname + '/custom-roles' 40 | 41 | The name of each custom role is the filename without extension. 42 | e.g. team-member.js : team-member 43 | 44 | @method createCustomRoleDefinitions 45 | @return {Object} 46 | ### 47 | createCustomRoleDefinitions: -> 48 | 49 | customRolePath = @facade.customRolePath ? @facade.dirname + '/custom-roles' 50 | 51 | if not fs.existsSync(customRolePath) or not fs.statSync(customRolePath).isDirectory() 52 | return null 53 | 54 | customRoles = {} 55 | 56 | for filename in fs.readdirSync(customRolePath) when filename.slice(-3) is '.js' 57 | roleName = filename.slice(0, -3) 58 | customRoles[roleName] = customRolePath + '/' + filename 59 | 60 | return customRoles 61 | 62 | 63 | 64 | ###* 65 | Create ModelDefinitions 66 | 67 | 1. load all the entities 68 | 2. check each entity's repository is LoopbackRepository 69 | 3. create ModelDefinition 70 | 4. add "hasMany" relations 71 | 5. add "hasManyThrough" relations 72 | 6. add custom relations 73 | 7. return object 74 | 75 | @method createModelDefinitions 76 | @private 77 | @return {Object} 78 | ### 79 | createModelDefinitions: -> 80 | 81 | definitions = {} 82 | 83 | for EntityModel in @getAllEntityModels() 84 | modelName = EntityModel.getName() 85 | try 86 | EntityRepository = @facade.require(modelName + '-repository') 87 | if (EntityRepository::) not instanceof LoopbackRepository 88 | debug('%s is not instance of LoopbackRepository', modelName + '-repository') 89 | continue 90 | catch e 91 | if e.message.match /model .*? is not found/ 92 | debug('%s does not have Repository', modelName) 93 | else 94 | debug('Error in reading repository of %s', modelName) 95 | debug(e.message) 96 | debug(e.stack) 97 | continue 98 | 99 | lbModelName = EntityRepository.getLbModelName() 100 | 101 | debug('model "%s" is added to model definition (loopback name: "%s")', modelName, lbModelName) 102 | 103 | definitions[lbModelName] = new ModelDefinition(EntityModel, EntityRepository, @facade) 104 | 105 | @setHasManyRelations(definitions) 106 | @setHasManyThroughRelation(definitions) 107 | 108 | for name, definition of definitions 109 | definition.addCustomRelations() 110 | definitions[name] = definition.export() 111 | 112 | debug('models for loopback: %s', Object.keys(definitions).join(', ')) 113 | 114 | return definitions 115 | 116 | 117 | ###* 118 | set "hasMany" relations 119 | 120 | @private 121 | ### 122 | setHasManyRelations: (definitions) -> 123 | 124 | for lbModelName, definition of definitions 125 | for prop, typeInfo of definition.getEntityProps() 126 | continue if typeInfo.isOutOfAggregate 127 | relLbModelName = @getLbModelName(typeInfo.model) 128 | continue if not relLbModelName 129 | relModelDefinition = definitions[relLbModelName] 130 | relModelDefinition?.setHasManyRelation(lbModelName, typeInfo.idPropName) 131 | 132 | 133 | ###* 134 | set "hasManyThrough" relations 135 | 136 | @private 137 | ### 138 | setHasManyThroughRelation: (definitions) -> 139 | 140 | for lbModelName, definition of definitions 141 | lbEntityProps = {} 142 | for prop, typeInfo of definition.getEntityProps() 143 | continue if typeInfo.isOutOfAggregate 144 | 145 | if @getLbModelName(typeInfo.model) 146 | lbEntityProps[prop] = typeInfo 147 | 148 | props = Object.keys(lbEntityProps) 149 | 150 | for propA, i in props 151 | propB = props[i + 1] 152 | break if not propB? 153 | 154 | typeInfoA = lbEntityProps[propA] 155 | typeInfoB = lbEntityProps[propB] 156 | 157 | modelA = @getLbModelName(typeInfoA.model) 158 | modelB = @getLbModelName(typeInfoB.model) 159 | 160 | defA = definitions[modelA] 161 | defB = definitions[modelB] 162 | 163 | defA.setHasManyThroughRelation 164 | model: modelB 165 | foreignKey: typeInfoA.idPropName 166 | keyThrough: typeInfoB.idPropName 167 | through: lbModelName 168 | 169 | defB.setHasManyThroughRelation 170 | model: modelA 171 | foreignKey: typeInfoB.idPropName 172 | keyThrough: typeInfoA.idPropName 173 | through: lbModelName 174 | 175 | 176 | getLbModelName: (modelName) -> 177 | try 178 | Repo = @facade.require(modelName + '-repository') 179 | return null if (Repo::) not instanceof LoopbackRepository 180 | return Repo.getLbModelName() 181 | catch e 182 | return null 183 | 184 | 185 | ###* 186 | get all entity models registered in domain facade 187 | 188 | @private 189 | ### 190 | getAllEntityModels: -> 191 | 192 | @loadAll() 193 | return (klass for name, klass of @facade.classes when klass.isEntity) 194 | 195 | 196 | ###* 197 | load all models in directory 198 | 199 | @private 200 | ### 201 | loadAll: -> 202 | 203 | return if not fs.existsSync @facade.dirname 204 | 205 | domainFiles = fs.readdirSync @facade.dirname 206 | 207 | for filename in domainFiles 208 | try 209 | [ name, ext ] = filename.split '.' 210 | continue if ext not in ['coffee', 'js'] 211 | @facade.require name 212 | catch e 213 | debug('Error in reading file: %s', filename) 214 | debug(e.message) 215 | debug(e.stack) 216 | 217 | 218 | module.exports = SettingExporter 219 | -------------------------------------------------------------------------------- /src/main-browser.coffee: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib/loopback-domain-facade') 2 | -------------------------------------------------------------------------------- /src/main.coffee: -------------------------------------------------------------------------------- 1 | LoopbackDomainFacade = require('./lib/loopback-domain-facade') 2 | LoopbackDomainFacade.SettingExporter = require('./lib/setting-exporter') 3 | 4 | module.exports = LoopbackDomainFacade 5 | -------------------------------------------------------------------------------- /webpack-test/index.js: -------------------------------------------------------------------------------- 1 | require('..') 2 | -------------------------------------------------------------------------------- /webpack-test/webpack.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line 2 | const webpack = require('webpack') 3 | module.exports = { 4 | context: __dirname, 5 | entry: { 6 | js: ['./index.js'] 7 | }, 8 | output: { 9 | path: `${__dirname}/dist`, 10 | filename: 'bundle.js' 11 | } 12 | } 13 | --------------------------------------------------------------------------------