├── .editorconfig ├── .gitignore ├── .jshintignore ├── .jshintrc ├── .travis.yml ├── .yo-rc.json ├── Dockerfile ├── README.md ├── Wechat.jpeg ├── alipay.jpeg ├── ci.sh ├── circle.yml ├── client ├── README.md ├── app_table.vue ├── app_version.vue ├── bundle.js ├── index.html ├── login.vue ├── main.js ├── select.vue ├── table.vue ├── version.vue └── zpagenav.vue ├── common └── models │ ├── app.js │ ├── app.json │ ├── appversion.js │ ├── appversion.json │ ├── version.js │ └── version.json ├── daovoice.png ├── data.json ├── deployment.yaml ├── jsconfig.json ├── package.json └── server ├── bin ├── automigration-db.js └── create-admin.js ├── boot ├── authentication.js └── root.js ├── component-config.json ├── config.json ├── datasources.json ├── middleware.json ├── middleware.production.json ├── model-config.json ├── private ├── certificate.pem ├── certrequest.csr ├── privatekey.pem └── ssl_cert.js └── server.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # http://editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | indent_style = space 9 | indent_size = 2 10 | end_of_line = lf 11 | charset = utf-8 12 | trim_trailing_whitespace = true 13 | insert_final_newline = true 14 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | /client/ 2 | /node_modules/ 3 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "node": true, 3 | "esnext": true, 4 | "bitwise": true, 5 | "camelcase": true, 6 | "eqeqeq": true, 7 | "eqnull": true, 8 | "immed": true, 9 | "indent": 2, 10 | "latedef": "nofunc", 11 | "newcap": true, 12 | "nonew": true, 13 | "noarg": true, 14 | "quotmark": "single", 15 | "regexp": true, 16 | "undef": true, 17 | "unused": false, 18 | "trailing": true, 19 | "sub": true, 20 | "maxlen": 80 21 | } 22 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4.1.2" 4 | 5 | script: 6 | - bash ./ci.sh 7 | -------------------------------------------------------------------------------- /.yo-rc.json: -------------------------------------------------------------------------------- 1 | { 2 | "generator-loopback": {} 3 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # FROM ubuntu 2 | # MAINTAINER longshao 3 | 4 | 5 | # RUN apt-get update 6 | # RUN apt-get install -y nodejs 7 | # #RUN apt-get install -y nodejs=0.6.12~dfsg1-1ubuntu1 8 | 9 | 10 | # # Run app using node 11 | # CMD apt-get update; apt-get install -y nodejs; npm install; node /server/server.js 12 | # # CMD ["/usr/bin/node", "/server/server.js"] 13 | 14 | FROM node:argon 15 | 16 | # Create app directory 17 | RUN mkdir -p /usr/src/app 18 | WORKDIR /usr/src/app 19 | 20 | # Install app dependencies 21 | COPY package.json /usr/src/app/ 22 | RUN npm install 23 | 24 | # Bundle app source 25 | COPY . /usr/src/app 26 | 27 | EXPOSE 4000 28 | 29 | # CMD [ "npm", "run build:js" ] 30 | #CMD [ "node", "." ] 31 | CMD npm run build:js && node . 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # generator-loopback-vue ``热更新,热部署 前后端分离实践`` 2 | [![License][license-img]][license-url] 3 | [![NPM Version][npm-img]][npm-url] 4 | [![Node Version][node-image]][node-url] 5 | [![Build Status][travis-img]][travis-url] 6 | [![Downloads][downloads-image]][downloads-url] 7 | 8 | [![NPM](https://nodei.co/npm/generator-loopback-vue.png?downloads=true&stars=true)](https://nodei.co/npm/generator-loopback-vue/) 9 | 10 | [travis-img]: https://travis-ci.org/qxl1231/generator-loopback-vue.svg?branch=master 11 | [travis-url]: https://travis-ci.org/qxl1231/generator-loopback-vue 12 | [npm-img]: https://img.shields.io/npm/v/generator-loopback-vue.svg 13 | [npm-url]: https://npmjs.org/package/generator-loopback-vue 14 | [david-img]: https://img.shields.io/david/qxl1231/generator-loopback-vue.svg 15 | [david-url]: https://david-dm.org/qxl1231/generator-loopback-vue 16 | [downloads-image]: https://img.shields.io/npm/dm/generator-loopback-vue.svg 17 | [downloads-url]: https://npmjs.org/package/generator-loopback-vue 18 | [license-img]: http://img.shields.io/badge/license-MIT-green.svg 19 | [license-url]: http://opensource.org/licenses/MIT 20 | [node-image]: https://img.shields.io/badge/node.js-v4.0.0-blue.svg 21 | [node-url]: http://nodejs.org/download/ 22 | 23 | ## 首先,为什么需要前后端分离?强烈推荐看下 24 |  25 | > http://2014.jsconf.cn/slides/herman-taobaoweb 26 | 27 | ## 以下是本项目的feature:(loopback中国讨论组-群号:575600225) 28 | - Strongloop是是如何通过一行命令和代码实现restful API接口,包括CRUD等14个接口 29 | - model是如何定义的,以及model-relation 定义,以及ACL接口权限控制等 30 | - CI持续集成的配置,Docker容器,docker部署文件 31 | - **如何登录鉴权,以及接口权限控制** 32 | - vue1.0/2.0 是如何玩?以及vue-resouce的使用,vue-router,webpack,babel等 33 | - loopback自带的authenticate 权限控制,accesstoken机制,credentials 34 | - strong-pm 部署命令,以及slc主要命令 35 | - 如何把项目部署在daocloud中,以及集成daovoice服务 36 | - ***增加了个vue-pagenav 组件的使用 实现分页功能*** 37 | 38 | 39 | ## Docker镜像 40 | > 最新版本: latest 41 | > 镜像地址: daocloud.io/qxl1231/lb-vue2 42 | 43 | ## 在线demo:(使用第三方daocloud平台(免费版),每天手动启动,如遇服务挂了,请见谅^.^) 44 | > http://loopback-vue.daoapp.io/ 45 | 帐号:test,密码:testpwd 46 | 47 | > http://loopback-vue.daoapp.io/explorer 48 | 49 | 50 | ## 部分截图 51 | ![image](https://cloud.githubusercontent.com/assets/8305742/17387903/810c8b16-5a2a-11e6-862a-9306067bfc34.png) 52 | > 集成了daovoice玩玩哈哈~~~炫酷!客服系统很赞,还有统计图标等,注意这不是广告!!确实不错哦 53 | ![image](./daovoice.png) 54 | 55 | ![image](https://cloud.githubusercontent.com/assets/8305742/17387949/dce5d7d0-5a2a-11e6-9e1d-5fe93b2924b2.png) 56 | 57 | The project is generated by [LoopBack](http://loopback.io).+[vue.js](http://vuejs.org). 58 | 59 | ## 如何启动:(国内用cnpm(先运行npm i cnpm -g),国外用npm) 60 | ```   61 | 1. npm i 62 | 2. 修改datasources.json 中的数据库配置比如:localhost:27017 63 | 3. node server/bin/create-admin.js 添加管理员帐号,密码 64 | 3.npm run build:js & node .(cold reload) --hot reload npm run watch:js 65 | ``` 66 | 67 | ## 遇到问题1:loopback+vue 不能运行 68 | > ```1.npm install 2.npm run build:js 3.node . ``` 69 | 70 | ## 问题2:热部署 71 | > To use hot reload, please try this command: 72 | ```npm run watch:js & node . ``` 73 | 74 | ## 热启动: 75 | > ```npm run watch:js & node . ``` 76 | 77 | 78 | ## 问题3:If you have error, try this: 79 | 80 | ``` 81 | npm install 82 | vueify-insert-css vue-hot-reload-api 83 | babel-core babel-preset-es2015 84 | babel-plugin-transform-runtime babel-runtime@5 85 | --save-dev 86 | ``` 87 | 88 | ## Hot reloading detail: 89 | > https://github.com/vuejs/vueify 90 | 91 | ## 其他help===>loopback 常用命令行: 92 | > - slc loopback 初始化项目 93 | > - slc loopback:datasource 94 | > - slc loopback:model 95 | > - slc loopback:relation 96 | > - slc loopback boot-script 97 | 98 | 99 | ## others:deploy and status 100 | 101 | > - slc deploy http://usr:pwd@localhost:port 102 | > - slpmctl -C http://usr:pwd@localhost:8701 ls 103 | 104 | > - slpmctl -C http://usr:pwd@domain:8701 status 105 | 106 | > - pm2 start -n weather app.js 107 | 108 | > - pm2 start -n app_update_server server.js 109 | 110 | ## LICENSE 111 | 112 | MIT 113 | 114 | 115 | ## 捐赠 116 | | No       | ID           | github | 117 | | ------------- |:-------------:| -----:| 118 | | 1 | JLF | https://github.com/cnJLF | 119 | | 2     | 萧大大 | https://github.com/freemember007 | 120 | | 3     | you     |   | 121 | | 4 | are next      |   | 122 | 123 | > ❤❤❤❤感谢我的兄弟们大力支持❤❤❤❤❤❤: 124 | - No.1    [JLF]:https://github.com/cnJLF 125 | - No.2    [萧大大]:https://github.com/freemember007 126 | 127 | 128 | ![支付宝](./alipay.jpeg) | ![微信](./Wechat.jpeg) 129 | -------------------------------------------------------------------------------- /Wechat.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qxl1231/generator-loopback-vue/60648743b1db9cfeedc09190639b87eea17af048/Wechat.jpeg -------------------------------------------------------------------------------- /alipay.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qxl1231/generator-loopback-vue/60648743b1db9cfeedc09190639b87eea17af048/alipay.jpeg -------------------------------------------------------------------------------- /ci.sh: -------------------------------------------------------------------------------- 1 | npm i 2 | npm run build:js 3 | 4 | 5 | -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | node: 3 | version: 5 4 | 5 | test: 6 | override: 7 | - bash ./ci.sh 8 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | ## Client 2 | 3 | This is the place for your application front-end files. 4 | -------------------------------------------------------------------------------- /client/app_table.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 136 | -------------------------------------------------------------------------------- /client/app_version.vue: -------------------------------------------------------------------------------- 1 | 46 | 47 | 159 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | appVersion 22 | 23 | 24 |
25 | 26 | 27 |
28 | 29 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /client/login.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 65 | 66 | 194 | -------------------------------------------------------------------------------- /client/main.js: -------------------------------------------------------------------------------- 1 | var Vue = require('vue'); 2 | var table = require('./table.vue'); 3 | var Version = require('./version.vue'); 4 | var login = require('./login.vue'); 5 | var select = require('./select.vue'); 6 | var app_version = require('./app_version.vue'); 7 | var app_table = require('./app_table.vue'); 8 | 9 | var VueRouter = require('vue-router'); 10 | 11 | Vue.use(require('vue-resource')); 12 | Vue.use(VueRouter); 13 | 14 | var router = new VueRouter(); 15 | 16 | var app = Vue.extend({}); 17 | 18 | router.map({ 19 | '/': { //登录 20 | component: login 21 | }, 22 | '/login': { 23 | component: login 24 | }, 25 | /* 404路由 */ 26 | '*': { 27 | component: login 28 | }, 29 | '/appversion': { //主页 30 | component: table 31 | }, 32 | '/select': { 33 | component: select 34 | }, 35 | '/app_version': { //主页 36 | component: app_table 37 | } 38 | }); 39 | 40 | router.start(app, "#app"); 41 | -------------------------------------------------------------------------------- /client/select.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 57 | -------------------------------------------------------------------------------- /client/table.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 158 | -------------------------------------------------------------------------------- /client/version.vue: -------------------------------------------------------------------------------- 1 | 64 | 65 | 196 | -------------------------------------------------------------------------------- /client/zpagenav.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | -------------------------------------------------------------------------------- /common/models/app.js: -------------------------------------------------------------------------------- 1 | module.exports = function (App) { 2 | App.prototype.checkVersion = function checkVersion(device_deploy_uuid, device_app_version, device_platform, cb) { 3 | var compatible_binary = false; 4 | var update_available = false; 5 | var comp_result = false; 6 | var zipurl = ''; 7 | 8 | this.versions(function (err, results) { 9 | if (err) { 10 | return cb(err); 11 | } 12 | var bestUpdate = {compatible_binary: false, update_available: false, device_deploy_uuid: '0.0.0', zipurl: '' }; 13 | 14 | for (i = 0; i < results.length; i++) { 15 | var latest_h5 = results[i]['h5Version']; 16 | var obj = results[i]; 17 | var max = results[i]['binMax']; 18 | var min = results[i]['binMin']; 19 | var versionType = results[i]['versionType']; 20 | 21 | if (device_platform.toLowerCase() == versionType.toLowerCase()) { 22 | var comp_result = compareH5(device_deploy_uuid, device_app_version, latest_h5, max, min, obj); 23 | 24 | if (comp_result.compatible_binary && !bestUpdate.compatible_binary) { 25 | bestUpdate = comp_result; 26 | } else if (comp_result.update_available && !bestUpdate.update_available) { 27 | bestUpdate = comp_result; 28 | } else if (comp_result.compatible_binary && comp_result.update_available && (compare(comp_result.device_deploy_uuid, bestUpdate.device_deploy_uuid) === 1)){ 29 | bestUpdate = comp_result; 30 | } 31 | } 32 | } 33 | 34 | cb(null, bestUpdate.compatible_binary, bestUpdate.update_available, {uuid: bestUpdate.device_deploy_uuid, url: bestUpdate.zipurl}); 35 | 36 | }); 37 | }; 38 | 39 | var compareH5 = function compareH5(device_deploy_uuid, device_app_version, latest_h5, max, min, obj, cb) { 40 | 41 | var update = {}; 42 | update.device_deploy_uuid = ''; 43 | update.zipurl = ''; 44 | update.compatible_binary = false; 45 | update.update_available = false; 46 | 47 | if (compare(device_app_version, min) >= 0 && compare(max, device_app_version) >= 0) { 48 | update.compatible_binary = true; 49 | if (compare(latest_h5, device_deploy_uuid) === 1) { 50 | update.update_available = true; 51 | update.device_deploy_uuid = latest_h5; 52 | update.zipurl = obj['url']; 53 | } 54 | } 55 | 56 | return update; 57 | }; 58 | 59 | var compare = function compare(v1, v2) { 60 | // var v1_tmp = v1, v2_tmp = v2; 61 | // v1 = v1.replace(/\./g, ''); 62 | // v2 = v2.replace(/\./g, ''); 63 | // // console.log("去掉小数点:", v1, v2); 64 | // var len = Math.max(v1.length, v2.length); 65 | // v1 = v1 + Array(len - v1.length + 1).join(0); 66 | // v2 = v2 + Array(len - v2.length + 1).join(0); 67 | // // console.log("补长对齐:", v1, v2); 68 | // v1 = parseInt(v1); 69 | // v2 = parseInt(v2); 70 | // // console.log("去掉前导0:", v1, v2); 71 | // // console.log("大的是:", Math.max(v1, v2)); 72 | // var ret = v1 > v2 ? 1 : v1 == v2 ? 0 : -1; 73 | // // console.log("compare:", v1_tmp, " ? ", v2_tmp, " : ", ret); 74 | // return ret; 75 | 76 | var newVersion = v1, curVersion = v2; 77 | if (curVersion === undefined) { 78 | curVersion = '0.0.0'; 79 | } 80 | console.log('newVersion: ' + newVersion + ' , curVersion: ' + curVersion); 81 | var newVersions = newVersion.split('.'); 82 | var curVersions = curVersion.split('.'); 83 | var length = newVersions.length; 84 | for (var i = 0; i < length; i++) { 85 | var newVersionValue = newVersions[i] === undefined ? 0 : newVersions[i]; 86 | var newVer = parseInt(newVersionValue); 87 | var curVersionValue = curVersions[i] === undefined ? 0 : curVersions[i]; 88 | var curVer = parseInt(curVersionValue); 89 | console.log('newVer : ' + newVer + ', curVer: ' + curVer); 90 | if (newVer < curVer) { 91 | return -1; 92 | } else if(newVer > curVer) { 93 | return 1; 94 | } 95 | } 96 | return 0; 97 | } 98 | 99 | App.remoteMethod('checkVersion', { 100 | isStatic: false, 101 | 102 | accepts: [ 103 | {arg: 'device_deploy_uuid', type: 'string'}, 104 | {arg: 'device_app_version', type: 'string'}, 105 | {arg: 'device_platform', type: 'string'} 106 | ], 107 | 108 | returns: [ 109 | {arg: 'compatible_binary', type: 'boolean'}, 110 | {arg: 'update_available', type: 'boolean'}, 111 | {arg: 'update', type: 'object'} 112 | ], 113 | 114 | http: {path: '/updates/check', verb: 'post'} 115 | }); 116 | 117 | App.prototype.checkAppVersion = function checkAppVersion(cb) { 118 | console.log('checkAppVersion......'); 119 | this.appversions(function (err, results) { 120 | if (err) { 121 | return cb(err); 122 | } 123 | var bestUpdate = results[0]; 124 | for (i = 0; i < results.length; i++) { 125 | var latest_verCode = results[i]['verCode']; 126 | var obj = results[i]; 127 | console.log('bestUpdate : ' + JSON.stringify(bestUpdate) + ' , latest_verCode: ' + latest_verCode); 128 | if (compare(latest_verCode, bestUpdate.verCode) === 1) { 129 | bestUpdate = obj; 130 | } 131 | } 132 | if (bestUpdate != null) { 133 | cb(null, bestUpdate.verCode, bestUpdate.releaseNotes, bestUpdate.apkPath); 134 | } else { 135 | cb(null, '', '', ''); 136 | } 137 | }); 138 | }; 139 | 140 | App.remoteMethod('checkAppVersion', { 141 | isStatic: false, 142 | 143 | accepts: [], 144 | 145 | returns: [ 146 | {arg: 'verCode', type: 'string'}, 147 | {arg: 'releaseNotes', type: 'string'}, 148 | {arg: 'apkPath', type: 'string'} 149 | ], 150 | 151 | http: {path: '/updates/checkApp', verb: 'get'} 152 | }); 153 | }; 154 | -------------------------------------------------------------------------------- /common/models/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "base": "PersistedModel", 4 | "idInjection": false, 5 | "options": { 6 | "validateUpsert": true 7 | }, 8 | "properties": { 9 | "id": { 10 | "type": "number", 11 | "id": true, 12 | "required": true 13 | }, 14 | "name": { 15 | "type": "string", 16 | "required": true 17 | }, 18 | "createTime": { 19 | "type": "date" 20 | }, 21 | "updateTime": { 22 | "type": "date" 23 | } 24 | }, 25 | "validations": [], 26 | "relations": { 27 | "versions": { 28 | "type": "hasMany", 29 | "model": "version", 30 | "foreignKey": "" 31 | }, 32 | "appversions": { 33 | "type": "hasMany", 34 | "model": "appversion", 35 | "foreignKey": "" 36 | } 37 | }, 38 | "acls": [ 39 | { 40 | "accessType": "*", 41 | "principalType": "ROLE", 42 | "principalId": "$everyone", 43 | "permission": "DENY" 44 | }, 45 | { 46 | "accessType": "*", 47 | "principalType": "ROLE", 48 | "principalId": "admin", 49 | "permission": "ALLOW" 50 | }, 51 | { 52 | "principalType": "ROLE", 53 | "principalId": "$everyone", 54 | "permission": "ALLOW", 55 | "property": "checkVersion" 56 | }, 57 | { 58 | "principalType": "ROLE", 59 | "principalId": "$everyone", 60 | "permission": "ALLOW", 61 | "property": "checkAppVersion" 62 | } 63 | ], 64 | "methods": {} 65 | } 66 | -------------------------------------------------------------------------------- /common/models/appversion.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(Appversion) { 4 | 5 | }; 6 | -------------------------------------------------------------------------------- /common/models/appversion.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "appversion", 3 | "base": "PersistedModel", 4 | "idInjection": true, 5 | "options": { 6 | "validateUpsert": true 7 | }, 8 | "properties": { 9 | "verCode": { 10 | "type": "string", 11 | "required": true 12 | }, 13 | "releaseNotes": { 14 | "type": "string", 15 | "required": true 16 | }, 17 | "apkPath": { 18 | "type": "string", 19 | "required": true 20 | } 21 | }, 22 | "validations": [], 23 | "relations": {}, 24 | "acls": [ 25 | { 26 | "accessType": "*", 27 | "principalType": "ROLE", 28 | "principalId": "$everyone", 29 | "permission": "DENY" 30 | }, 31 | { 32 | "accessType": "*", 33 | "principalType": "ROLE", 34 | "principalId": "admin", 35 | "permission": "ALLOW" 36 | } 37 | ], 38 | "methods": {} 39 | } 40 | -------------------------------------------------------------------------------- /common/models/version.js: -------------------------------------------------------------------------------- 1 | module.exports = function(Version) { 2 | 3 | }; 4 | -------------------------------------------------------------------------------- /common/models/version.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "version", 3 | "base": "PersistedModel", 4 | "idInjection": true, 5 | "options": { 6 | "validateUpsert": true 7 | }, 8 | "properties": { 9 | "versionType": { 10 | "type": "string", 11 | "required": true 12 | }, 13 | "url": { 14 | "type": "string" 15 | }, 16 | "createTime": { 17 | "type": "date" 18 | }, 19 | "h5Version": { 20 | "type": "string" 21 | }, 22 | "binMax": { 23 | "type": "string" 24 | }, 25 | "binMin": { 26 | "type": "string" 27 | }, 28 | "updateTime": { 29 | "type": "date" 30 | } 31 | }, 32 | "validations": [], 33 | "relations": {}, 34 | "acls": [ 35 | { 36 | "accessType": "*", 37 | "principalType": "ROLE", 38 | "principalId": "$everyone", 39 | "permission": "DENY" 40 | }, 41 | { 42 | "accessType": "*", 43 | "principalType": "ROLE", 44 | "principalId": "admin", 45 | "permission": "ALLOW" 46 | } 47 | ], 48 | "methods": {} 49 | } 50 | -------------------------------------------------------------------------------- /daovoice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qxl1231/generator-loopback-vue/60648743b1db9cfeedc09190639b87eea17af048/daovoice.png -------------------------------------------------------------------------------- /data.json: -------------------------------------------------------------------------------- 1 | { 2 | "ids": { 3 | "User": 2, 4 | "AccessToken": 1, 5 | "ACL": 1, 6 | "RoleMapping": 2, 7 | "Role": 2, 8 | "app": 1, 9 | "version": 1, 10 | "appversion": 1 11 | }, 12 | "models": { 13 | "User": { 14 | "1": "{\"username\":\"test\",\"password\":\"$2a$10$6lu5b7RFyuJFG8Nf.PfaLO6Qz/EGYPCrXO7zD1tzxc/uHw9LQ29Li\",\"id\":1,\"emailVerified\":true,\"verificationToken\":null}" 15 | }, 16 | "AccessToken": {}, 17 | "ACL": {}, 18 | "RoleMapping": { 19 | "1": "{\"principalType\":\"USER\",\"principalId\":\"1\",\"roleId\":1,\"id\":1}" 20 | }, 21 | "Role": { 22 | "1": "{\"name\":\"admin\",\"created\":\"2016-11-30T00:45:55.533Z\",\"modified\":\"2016-11-30T00:45:55.533Z\",\"id\":1}" 23 | }, 24 | "app": {}, 25 | "version": {}, 26 | "appversion": {} 27 | } 28 | } -------------------------------------------------------------------------------- /deployment.yaml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "module": "commonjs" 5 | } 6 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generator-loopback-vue", 3 | "version": "2.1.4", 4 | "main": "server/server.js", 5 | "scripts": { 6 | "build:js": "browserify -t vueify ${npm_package_config_src_js} > ${npm_package_config_dist_js}", 7 | "pretest": "jshint .", 8 | "watch:js": "watchify -p browserify-hmr -t vueify ${npm_package_config_src_js} -o ${npm_package_config_dist_js} -dv" 9 | }, 10 | "dependencies": { 11 | "compression": "^1.0.3", 12 | "cors": "^2.5.2", 13 | "loopback": "^2.22.0", 14 | "loopback-boot": "^2.6.5", 15 | "loopback-component-explorer": "^2.1.0", 16 | "loopback-connector-mongodb": "^1.13.0", 17 | "loopback-datasource-juggler": "^2.39.0", 18 | "serve-favicon": "^2.0.1", 19 | "vue-pagenav": "^1.5.0", 20 | "vue-resource": "^0.1.17", 21 | "vue-router": "^0.7.11", 22 | "vueify": "^6.0.0" 23 | }, 24 | "devDependencies": { 25 | "babel-plugin-transform-runtime": "^6.1.2", 26 | "babel-preset-es2015": "^6.1.2", 27 | "babel-runtime": "^6.0.14", 28 | "browserify": "^12.0.1", 29 | "browserify-hmr": "^0.3.1", 30 | "jshint": "^2.5.6", 31 | "mermaid": "^0.5.5", 32 | "vue": "^1.0.7", 33 | "vue-hot-reload-api": "^1.2.1", 34 | "vueify-insert-css": "^1.0.0", 35 | "watchify": "^3.6.0" 36 | }, 37 | "repository": { 38 | "type": "git", 39 | "url": "https://github.com/qxl1231/generator-loopback-vue.git" 40 | }, 41 | "keywords": [ 42 | "loopback-vue"," yeoman-generator", 43 | "node", 44 | "js" 45 | ], 46 | "author": "qiangxl:wechat(278931058)", 47 | "license": "MIT", 48 | "description": "loopback+vue+vue-resource,前后端分离模板,vue page分页功能,authenticate 权限控制,accesstoken机制,credentials,CI,docker ,hot reload vue", 49 | "config": { 50 | "src_js": "client/main.js", 51 | "dist_js": "client/bundle.js" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /server/bin/automigration-db.js: -------------------------------------------------------------------------------- 1 | /* jshint node:true */ 2 | 'use strict'; 3 | var app = require('../server'); 4 | 5 | var dataSource = app.dataSources.appversion; 6 | 7 | dataSource.automigrate(function(err){ 8 | if (err) { 9 | console.log("Migrate model db error:", err); 10 | return; 11 | } 12 | 13 | console.log("Migrate model db success"); 14 | dataSource.disconnect(function(err){ 15 | if (err) throw err; 16 | }); 17 | }); 18 | 19 | 20 | -------------------------------------------------------------------------------- /server/bin/create-admin.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | var app = require(path.resolve(__dirname, '../server')); 3 | 4 | app.models.User.validations.email = 5 | [ 6 | { validation: 'format', 7 | allowNull: true, 8 | allowBlank: true, 9 | message: 'Must provide a valid email', 10 | with: /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, 11 | options: {} }, 12 | { validation: 'uniqueness', 13 | message: 'Email already exists', 14 | options: {async: true}} 15 | ]; 16 | 17 | app.models.User.create([{ 18 | username: 'test', 19 | password: 'testpwd' 20 | }], function(err, users) { 21 | if (err) throw err; 22 | console.log('Admin account created.'); 23 | 24 | app.models.User.generateVerificationToken(users[0].id, function(err, token){ 25 | app.models.User.confirm(users[0].id, token.id, null, function(err, result) { 26 | if (err) throw err; 27 | console.log('Automatic verified; Ok to login'); 28 | }); 29 | }); 30 | 31 | //create the admin role 32 | app.models.Role.create({ 33 | name: 'admin' 34 | }, function(err, role) { 35 | if (err) throw err; 36 | 37 | console.log('Created role:', role); 38 | 39 | //assign an admin 40 | role.principals.create({ 41 | principalType: app.models.RoleMapping.USER, 42 | principalId: users[0].id 43 | }, function(err, principal) { 44 | if (err) throw err; 45 | 46 | console.log('Created principal:', principal); 47 | // app.dataSources.appversion.disconnect(function(err){ 48 | // if (err) throw err; 49 | // }); 50 | }); 51 | }); 52 | }); 53 | -------------------------------------------------------------------------------- /server/boot/authentication.js: -------------------------------------------------------------------------------- 1 | module.exports = function enableAuthentication(server) { 2 | // enable authentication 3 | server.enableAuth(); 4 | }; 5 | -------------------------------------------------------------------------------- /server/boot/root.js: -------------------------------------------------------------------------------- 1 | module.exports = function(server) { 2 | // Install a `/` route that returns server status 3 | var router = server.loopback.Router(); 4 | //router.get('/', server.loopback.status()); 5 | server.use(router); 6 | }; 7 | -------------------------------------------------------------------------------- /server/component-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "loopback-component-explorer": { 3 | "mountPath": "/explorer" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /server/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "restApiRoot": "/api/v1", 3 | "host": "0.0.0.0", 4 | "port": 4000, 5 | "https-port": 4015, 6 | "remoting": { 7 | "context": { 8 | "enableHttpContext": false 9 | }, 10 | "rest": { 11 | "normalizeHttpPath": false, 12 | "xml": false 13 | }, 14 | "json": { 15 | "strict": false, 16 | "limit": "100kb" 17 | }, 18 | "urlencoded": { 19 | "extended": true, 20 | "limit": "100kb" 21 | }, 22 | "cors": false, 23 | "errorHandler": { 24 | "disableStackTrace": false 25 | } 26 | }, 27 | "legacyExplorer": false 28 | } 29 | -------------------------------------------------------------------------------- /server/datasources.json: -------------------------------------------------------------------------------- 1 | { 2 | "db": { 3 | "name": "db", 4 | "connector": "memory", 5 | "file":"data.json" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /server/middleware.json: -------------------------------------------------------------------------------- 1 | { 2 | "initial:before": { 3 | "loopback#favicon": {} 4 | }, 5 | "initial": { 6 | "compression": {}, 7 | "cors": { 8 | "params": { 9 | "origin": true, 10 | "credentials": true, 11 | "maxAge": 86400 12 | } 13 | } 14 | }, 15 | "session": {}, 16 | "auth": {}, 17 | "parse": {}, 18 | "routes": { 19 | "loopback#rest": { 20 | "paths": [ 21 | "${restApiRoot}" 22 | ] 23 | } 24 | }, 25 | "files": { 26 | "loopback#static": { 27 | "params": "$!../client" 28 | } 29 | }, 30 | "final": { 31 | "loopback#urlNotFound": {} 32 | }, 33 | "final:after": { 34 | "loopback#errorHandler": {} 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /server/middleware.production.json: -------------------------------------------------------------------------------- 1 | { 2 | "final:after": { 3 | "loopback#errorHandler": { 4 | "params": { 5 | "includeStack": false 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /server/model-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "_meta": { 3 | "sources": [ 4 | "loopback/common/models", 5 | "loopback/server/models", 6 | "../common/models", 7 | "./models" 8 | ], 9 | "mixins": [ 10 | "loopback/common/mixins", 11 | "loopback/server/mixins", 12 | "../common/mixins", 13 | "./mixins" 14 | ] 15 | }, 16 | "User": { 17 | "dataSource": "db" 18 | }, 19 | "AccessToken": { 20 | "dataSource": "db", 21 | "public": false 22 | }, 23 | "ACL": { 24 | "dataSource": "db", 25 | "public": false 26 | }, 27 | "RoleMapping": { 28 | "dataSource": "db", 29 | "public": false 30 | }, 31 | "Role": { 32 | "dataSource": "db", 33 | "public": false 34 | }, 35 | "app": { 36 | "dataSource": "db", 37 | "public": true 38 | }, 39 | "version": { 40 | "dataSource": "db", 41 | "public": true 42 | }, 43 | "appversion": { 44 | "dataSource": "db", 45 | "public": true 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /server/private/certificate.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICQTCCAaoCCQCH2M7U6KhP0zANBgkqhkiG9w0BAQUFADBlMQswCQYDVQQGEwJD 3 | TjENMAsGA1UECBMEd3V4aTENMAsGA1UEBxMEd3V4aTETMBEGA1UEChMKTGl0dGxl 4 | c3dhbjEjMCEGCSqGSIb3DQEJARYUbGl0dGxlc3dhbkBtaWRlYS5jb20wHhcNMTYx 5 | MjEzMDY1ODI1WhcNMTcwMTEyMDY1ODI1WjBlMQswCQYDVQQGEwJDTjENMAsGA1UE 6 | CBMEd3V4aTENMAsGA1UEBxMEd3V4aTETMBEGA1UEChMKTGl0dGxlc3dhbjEjMCEG 7 | CSqGSIb3DQEJARYUbGl0dGxlc3dhbkBtaWRlYS5jb20wgZ8wDQYJKoZIhvcNAQEB 8 | BQADgY0AMIGJAoGBAK+C3Y1uhhJAY2H3st2OVa8PnczcniO5B5AWyP+Z0TjsWJwv 9 | lfKYHDmMDVpSlHJC+//8+rZZPg6IEWPnB1oIRzyr+CXnpqxORNfq28xdFHve7pzy 10 | SxEwKjN0WFf8fW0ylxxaL4v5nymGLy5fBacinv9RCJ9F6zlWppxlvzSvEG+xAgMB 11 | AAEwDQYJKoZIhvcNAQEFBQADgYEAkYoWAa+LuBIF72gWU7F+qllXiLYZDP+ZnBgK 12 | xOwwjZ57kFbrV/m0IbONYboxWtTc+hjNhkMSXOt3HPzAetJtcwmmq/Ofk0ry6OhV 13 | T+YwiwHjMEJQYOxYf/wVv9J5ubxDt/ijd26Or2sL5qaSczO6kYziMBVxStUXoGtG 14 | MpVzImA= 15 | -----END CERTIFICATE----- 16 | -------------------------------------------------------------------------------- /server/private/certrequest.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIBvjCCAScCAQAwZTELMAkGA1UEBhMCQ04xDTALBgNVBAgTBHd1eGkxDTALBgNV 3 | BAcTBHd1eGkxEzARBgNVBAoTCkxpdHRsZXN3YW4xIzAhBgkqhkiG9w0BCQEWFGxp 4 | dHRsZXN3YW5AbWlkZWEuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCv 5 | gt2NboYSQGNh97LdjlWvD53M3J4juQeQFsj/mdE47FicL5XymBw5jA1aUpRyQvv/ 6 | /Pq2WT4OiBFj5wdaCEc8q/gl56asTkTX6tvMXRR73u6c8ksRMCozdFhX/H1tMpcc 7 | Wi+L+Z8phi8uXwWnIp7/UQifRes5VqacZb80rxBvsQIDAQABoBkwFwYJKoZIhvcN 8 | AQkHMQoTCDEyMzQ1Njc4MA0GCSqGSIb3DQEBBQUAA4GBAClB/ND2vpOqmokHLWKa 9 | s5x5SLaA2uVbB+hv7Qquhi7Y72F/6SVN6VXdyd7J2VV8xLdQXijuiJiPU+KIphb7 10 | 5UInRTkxUU6ZxMT8iTCZ88WgflZGcAS3F8SuOQlWpQb8KrCVW32e2JYfg5HehfNl 11 | DGYhTVtvJjD4Xf4ssjbx3ixi 12 | -----END CERTIFICATE REQUEST----- 13 | -------------------------------------------------------------------------------- /server/private/privatekey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICWwIBAAKBgQCvgt2NboYSQGNh97LdjlWvD53M3J4juQeQFsj/mdE47FicL5Xy 3 | mBw5jA1aUpRyQvv//Pq2WT4OiBFj5wdaCEc8q/gl56asTkTX6tvMXRR73u6c8ksR 4 | MCozdFhX/H1tMpccWi+L+Z8phi8uXwWnIp7/UQifRes5VqacZb80rxBvsQIDAQAB 5 | AoGABAvpXcRplOwlHnIejpskgJfElJ+Vic9PTFQqKDJ8r2pLlLZIJ8K4C9+dwzJa 6 | N9QxiTJk+WVPV/htJjeCZOIB+mtaNTNBV7o4ACseCCGJdgnjxwTRtrHrNamwdoLb 7 | He+ie3q2OR8DN3W8h1QYUN6DPr9QZ5O89UJHa3PX/zMjJq0CQQDWbm3bC4pRygPk 8 | kJKjE9jMH4tMfESEl5+xjB9TDWDXCEESJrIgPErPTPiVIqiDJGopDIMOnWXi1OPR 9 | c78r7NODAkEA0Yjyl3EeVU3dfgr4vaEnVZfaJRc513g3x9vxnnz1smbaZsAyP6lF 10 | OVecL0M70GxOlLq7HJ0d9Xuy8ZJFX4AluwJAcKbnM9KmQj29OdDyGJaOqP5Rckc+ 11 | v+HgVahltH4syAtgFCccIW9LZgjr932TxgHVe+dE2uK56icp4JnMb0kl/QJATW4I 12 | B4KbrPY7NUqkpJ6axHx69l3g2AjqxAY5AVI4ERcE+hdn+jJ5DDmd07FKtzhuyXM/ 13 | ldFRstdlRvPAgwv95QJAHqLbQRH797nxoBbiK/f+vTA70THq3/HftOsWRPt+SCDa 14 | 24cmL5qMMGKl1fUidpqFB+3/YTN2F+3QXxJrEFR6mA== 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /server/private/ssl_cert.js: -------------------------------------------------------------------------------- 1 | // Copyright IBM Corp. 2014,2015. All Rights Reserved. 2 | // Node module: strong-gateway 3 | // US Government Users Restricted Rights - Use, duplication or disclosure 4 | // restricted by GSA ADP Schedule Contract with IBM Corp. 5 | 6 | var crypto = require('crypto'); 7 | var tls = require('tls'); 8 | var fs = require('fs'); 9 | var path = require('path'); 10 | 11 | exports.privateKey = fs.readFileSync(path.join(__dirname, 'privatekey.pem')) 12 | .toString(); 13 | exports.certificate = fs.readFileSync(path.join(__dirname, 'certificate.pem')) 14 | .toString(); 15 | 16 | if (typeof tls.createSecureContext === 'function') { 17 | exports.credentials = tls.createSecureContext( 18 | {key: exports.privateKey, cert: exports.certificate}); 19 | } else { 20 | exports.credentials = crypto.createCredentials( 21 | {key: exports.privateKey, cert: exports.certificate}); 22 | } 23 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | // var loopback = require('loopback'); 2 | // var boot = require('loopback-boot'); 3 | // var path = require('path'); 4 | // 5 | // var app = module.exports = loopback(); 6 | // 7 | // app.start = function () { 8 | // // start the web server 9 | // return app.listen(function () { 10 | // app.emit('started'); 11 | // var baseUrl = app.get('url').replace(/\/$/, ''); 12 | // console.log('Web server listening at: %s', baseUrl); 13 | // if (app.get('loopback-component-explorer')) { 14 | // var explorerPath = app.get('loopback-component-explorer').mountPath; 15 | // console.log('Browse your REST API at %s%s', baseUrl, explorerPath); 16 | // } 17 | // }); 18 | // }; 19 | // 20 | // //var staticPath = null; 21 | // // 22 | // //staticPath = path.resolve(__dirname, '../client/'); 23 | // //console.log("Running app in development mode"); 24 | // // 25 | // //app.use(loopback.static(staticPath)); 26 | // 27 | // // Bootstrap the application, configure models, datasources and middleware. 28 | // // Sub-apps like REST API are mounted via boot scripts. 29 | // boot(app, __dirname, function (err) { 30 | // if (err) throw err; 31 | // 32 | // // start the server if `$ node server.js` 33 | // if (require.main === module) 34 | // app.start(); 35 | // }); 36 | 37 | var loopback = require('loopback'); 38 | var boot = require('loopback-boot'); 39 | 40 | var http = require('http'); 41 | var https = require('https'); 42 | var sslCert = require('./private/ssl_cert'); 43 | var httpsOptions = { 44 | key: sslCert.privateKey, 45 | cert: sslCert.certificate 46 | }; 47 | var app = module.exports = loopback(); 48 | 49 | // boot scripts mount components like REST API 50 | boot(app, __dirname); 51 | 52 | app.start = function() { 53 | // start the web server 54 | // return app.listen(function() { 55 | // app.emit('started'); 56 | // var baseUrl = app.get('url').replace(/\/$/, ''); 57 | // console.log('Web server listening at: %s', baseUrl); 58 | // if (app.get('loopback-component-explorer')) { 59 | // var explorerPath = app.get('loopback-component-explorer').mountPath; 60 | // console.log('Browse your REST API at %s%s', baseUrl, explorerPath); 61 | // } 62 | // }); 63 | 64 | var port = app.get('port'); 65 | var host = app.get('host'); 66 | var httpServer = http.createServer(app).listen(port, host, function() { 67 | 68 | var httpsPort = app.get('https-port'); 69 | var httpsServer = https.createServer(httpsOptions, app).listen(httpsPort, 70 | host, function() { 71 | 72 | app.emit('started'); 73 | 74 | app.close = function(cb) { 75 | app.removeAllListeners('started'); 76 | app.removeAllListeners('loaded'); 77 | httpServer.close(function() { 78 | httpsServer.close(cb); 79 | }); 80 | }; 81 | }); 82 | }); 83 | }; 84 | 85 | // start the server if `$ node server.js` 86 | if (require.main === module) { 87 | app.start(); 88 | } --------------------------------------------------------------------------------