From 0ed26bd85a9876cde5d8019afa164fa12a5efb86 Mon Sep 17 00:00:00 2001 From: Walker Leite Date: Mon, 11 Dec 2017 17:56:07 -0200 Subject: [PATCH 01/25] add project title --- template/client/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/template/client/index.html b/template/client/index.html index ccf1dad..59b292c 100644 --- a/template/client/index.html +++ b/template/client/index.html @@ -2,7 +2,7 @@ - + {{name}}
From f08bb6f91bac00b6294838a1f8ee5ac4dddc0d1a Mon Sep 17 00:00:00 2001 From: Walker Leite Date: Mon, 11 Dec 2017 19:51:19 -0200 Subject: [PATCH 02/25] feat(template): add extended option --- meta.js | 38 ++-- template/.babelrc | 14 +- template/.eslintrc | 5 +- template/.gitlab-ci.yml | 24 +++ template/client/App.vue | 10 +- .../components/HelloWorld/hello-world.vue | 9 + template/client/index.html | 3 + template/client/main.js | 33 ++++ template/client/router.js | 56 ++++++ template/client/services/loopback.js | 37 ++++ template/client/static/images/logo.png | Bin 0 -> 4178 bytes template/client/store/index.js | 21 ++ template/client/store/modules/auth/actions.js | 78 ++++++++ template/client/store/modules/auth/index.js | 13 ++ .../client/store/modules/auth/mutations.js | 15 ++ template/client/style/_spinner.scss | 22 +++ template/client/style/app.scss | 16 ++ template/client/style/global.scss | 54 +++++ template/client/view/Dashboard.vue | 27 +++ template/client/view/Login.vue | 186 ++++++++++++++++++ template/client/view/Profile.vue | 147 ++++++++++++++ .../view/containers/HeaderContainer.vue | 45 +++++ template/client/view/mixins/notification.js | 17 ++ template/gulp-tasks/build.js | 29 ++- template/gulp-tasks/config.js | 1 + template/gulp-tasks/copy.js | 7 +- template/gulp-tasks/serve.js | 8 +- template/package.json | 23 ++- template/server/boot/add-initial-data.js | 34 ++++ template/server/boot/create-admin.js | 51 +++++ template/server/datasources.json | 20 ++ .../initial-data/maintenance-account.json | 4 + template/server/model-config.json | 47 ++++- template/server/models/account.js | 20 ++ template/server/models/account.json | 47 +++++ template/test/server/account.test.js | 29 +++ template/test/server/boot.test.js | 65 ++++-- 37 files changed, 1206 insertions(+), 49 deletions(-) create mode 100644 template/.gitlab-ci.yml create mode 100644 template/client/components/HelloWorld/hello-world.vue create mode 100644 template/client/router.js create mode 100644 template/client/services/loopback.js create mode 100644 template/client/static/images/logo.png create mode 100644 template/client/store/index.js create mode 100644 template/client/store/modules/auth/actions.js create mode 100644 template/client/store/modules/auth/index.js create mode 100644 template/client/store/modules/auth/mutations.js create mode 100644 template/client/style/_spinner.scss create mode 100644 template/client/style/app.scss create mode 100644 template/client/style/global.scss create mode 100644 template/client/view/Dashboard.vue create mode 100644 template/client/view/Login.vue create mode 100644 template/client/view/Profile.vue create mode 100644 template/client/view/containers/HeaderContainer.vue create mode 100644 template/client/view/mixins/notification.js create mode 100644 template/server/boot/add-initial-data.js create mode 100644 template/server/boot/create-admin.js create mode 100644 template/server/initial-data/maintenance-account.json create mode 100644 template/server/models/account.js create mode 100644 template/server/models/account.json create mode 100644 template/test/server/account.test.js diff --git a/meta.js b/meta.js index 8e6aa5f..9f689e4 100644 --- a/meta.js +++ b/meta.js @@ -3,7 +3,6 @@ const path = require('path'); module.exports = { "prompts": { - "name": { "type": "string", "required": true, @@ -19,26 +18,25 @@ module.exports = { "type": "string", "message": "Author" }, - "build": { - "type": "list", - "message": "Vue build", - "choices": [ - { - "name": "Runtime + Compiler: recommended for most users", - "value": "standalone", - "short": "standalone" - }, - { - "name": "Runtime-only: about 6KB lighter min+gzip, but templates (or any Vue-specific HTML) are ONLY allowed in .vue files - render functions are required elsewhere", - "value": "runtime", - "short": "runtime" - } - ] - }, - "router": { + "extended": { "type": "confirm", - "message": "Install vue-router?" - }, + "message": "Add basic Login and Admin views with Vuex, Vue-router and Bootstrap-vue?" + } + }, + "filters": { + "client/router.js": "extended", + "client/static/main.css": "extended === false", + "client/static/images/**/*": "extended", + "client/components/**/*": "extended", + "client/services/**/*": "extended", + "client/store/**/*": "extended", + "client/style/**/*": "extended", + "client/view/**/*": "extended", + "server/boot/add-initial-data.js": "extended", + "server/boot/create-admin.js": "extended", + "server/initial-data/**/*": "extended", + "server/models/**/*": "extended", + "test/server/account.test.js": "extended", }, "complete": function(data, {logger}) { logger.log("To get started:"); diff --git a/template/.babelrc b/template/.babelrc index c13c5f6..6dd4fd5 100644 --- a/template/.babelrc +++ b/template/.babelrc @@ -1,3 +1,15 @@ { - "presets": ["es2015"] + "presets": ["es2015"], + "plugins": [ + [ + "babel-plugin-root-import", [{ + "rootPathPrefix": "~", + "rootPathSuffix": "." + }, { + "rootPathPrefix": "@", + "rootPathSuffix": "client" + } + ] + ] + ] } diff --git a/template/.eslintrc b/template/.eslintrc index 0f5d35c..1b4b9af 100644 --- a/template/.eslintrc +++ b/template/.eslintrc @@ -6,6 +6,9 @@ "sourceType": "module" }, "globals": { - "require": true + "expect": true, + "assert": true, + "require": true, + "request": true } } diff --git a/template/.gitlab-ci.yml b/template/.gitlab-ci.yml new file mode 100644 index 0000000..4cfe2bb --- /dev/null +++ b/template/.gitlab-ci.yml @@ -0,0 +1,24 @@ +# Official framework image. Look for the different tagged releases at: +# https://hub.docker.com/r/library/node/tags/ +image: node:latest + +before_script: + - npm install + +# This folder is cached between builds +# http://docs.gitlab.com/ce/ci/yaml/README.html#cache +cache: + paths: + - node_modules/ + +test:lint: + script: + - npm run lint + +test:vulnerabilities: + script: + - npm run vulnerabilities + +test:node:latest: + script: + - npm run test diff --git a/template/client/App.vue b/template/client/App.vue index c7cd117..0bd8e58 100644 --- a/template/client/App.vue +++ b/template/client/App.vue @@ -1,14 +1,20 @@ - diff --git a/template/client/components/HelloWorld/hello-world.vue b/template/client/components/HelloWorld/hello-world.vue new file mode 100644 index 0000000..9065479 --- /dev/null +++ b/template/client/components/HelloWorld/hello-world.vue @@ -0,0 +1,9 @@ + + + diff --git a/template/client/index.html b/template/client/index.html index 59b292c..d310495 100644 --- a/template/client/index.html +++ b/template/client/index.html @@ -3,6 +3,9 @@ {{name}} + {{#extended}} + + {{/extended}}
diff --git a/template/client/main.js b/template/client/main.js index f0ced40..5fefacb 100644 --- a/template/client/main.js +++ b/template/client/main.js @@ -1,9 +1,42 @@ +{{#extended}} +/* Required by BootstrapVue */ +import 'babel-polyfill'; + +{{/extended}} +/* Global Components */ import Vue from 'vue'; +{{#extended}} +import {sync} from 'vuex-router-sync'; +import 'bootstrap-vue/dist/bootstrap-vue.css'; +import BootstrapVue from 'bootstrap-vue'; +// import 'vue-awesome/icons'; +import Icon from 'vue-awesome'; + +Vue.use(BootstrapVue); + +Vue.component('icon', Icon); + +{{/extended}} +/* Local Components and modules */ import App from './App.vue'; +{{#extended}} +import router from './router.js'; +import store from './store'; + +// Add router state to store +sync(store, router); +{{/extended}} +{{unless extended}} import './static/main.css'; +{{/unless}} +// Instance Application new Vue({ el: '#app', render: (r) => r(App), + {{#extended}} + router, + store, + {{/extended}} }); diff --git a/template/client/router.js b/template/client/router.js new file mode 100644 index 0000000..e2bc303 --- /dev/null +++ b/template/client/router.js @@ -0,0 +1,56 @@ +import Vue from 'vue'; +import VueRouter from 'vue-router'; +import store from '@/store'; +import Login from './view/Login.vue'; +import Dashboard from './view/Dashboard.vue'; +import Profile from './view/Profile.vue'; + +Vue.use(VueRouter); + +const router = new VueRouter({ + mode: 'history', + routes: [ + { + path: '/', + name: 'home', + redirect: {name: 'dashboard'}, + }, { + path: '/dashboard', + name: 'dashboard', + component: Dashboard, + meta: {requiresAuth: true}, + }, { + path: '/login', + name: 'login', + component: Login, + }, { + path: '/profile', + name: 'profile', + component: Profile, + // eslint-disable-next-line camelcase + props: route => ({access_token: route.query.access_token}), + meta: {requiresAuth: true}, + }, + ], +}); + +router.beforeEach((to, from, next) => { + if (to.matched.some(record => record.meta.requiresAuth)) { + // this route requires auth, check if logged in + // if not, redirect to login page (except when it's profile route and + // there is an access_token). + if (to.name === 'profile' && to.query.access_token) { + next(); + } else if (!store.state.auth.access_token) { + next({ + name: 'login', + }); + } else { + next(); + } + } else { + next(); // make sure to always call next()! + } +}); + +export default router; diff --git a/template/client/services/loopback.js b/template/client/services/loopback.js new file mode 100644 index 0000000..7fedac7 --- /dev/null +++ b/template/client/services/loopback.js @@ -0,0 +1,37 @@ +import {host, restApiRoot, port} from '~/server/config.json'; +import axios from 'axios'; + +const http = axios.create({ + baseURL: 'http://' + host + ':' + port + restApiRoot, +}); + +http.setToken = token => { + http.defaults.headers.common['Authorization'] = token; +}; + +http.removeToken = () => { + delete http.defaults.headers.common.Authorization; +}; + +http.find = (endpoint, filter) => http.get(endpoint, {params: {filter}}); + +const interceptErrors = err => { + try { + err = Object.assign(new Error(), err.response.data.error); + } catch (e) { + // Will return err if something goes wrong + } + return Promise.reject(err); +}; +const interceptResponse = res => { + try { + return res.data; + } catch (e) { + return res; + } +}; +http.interceptors.response.use(interceptResponse, interceptErrors); + +export default http; + +// Documentation: https://github.com/axios/axios diff --git a/template/client/static/images/logo.png b/template/client/static/images/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..74389d8ca08257a23e73da530529831d900cf00a GIT binary patch literal 4178 zcmaJ^dpMM78<+O$KuonlY@;?>lT$fnPI9UkMJSW=5H&Hw7>5a?O)-gyG^nIjHB{u( znsF%Ynl)O|0iQ5Vk+EtFQsXeu_q;=W`?c5gUDx~G&+qv?_u+Tn?=yc)ipzn$N(veZ za&mG?4*Tp}<>Z!SK<}#MfZ-DO3vzPuhAvL-_D)2Kb=4X0pkpJfuE#Ip#qC$SNCJny zGpLha0>1(p#GqwcG%Xy$i#7(r(Dn;}aP$}Q3qVDEVIsqp{AB?BbxU31Q4!=t2NVSY zD9REq!%IgH4kInPA!PtXUfQBaU-;0_C5I3QAY~Q;CuCb<$kOTY6H6d?30NK_0>&~B zvJfB~UjqKe0NCXVSqOLsF9FXW04(rFA?pEI2)G9VYwj9g?m_^-Y`?#mgRhyRuNltQ z%*hXI&7J+t4-(AX0xS*%g4kVNK_IiG|1nSw2tWp(Kps1RJbnVm1|5O!P~l`WH4GIU zj-rKwkA;qpz$8%7r>U4Tk(lHtOiDC1H3pj&gH5MlFP_A*Vy$vwt**pjbK}5wxB7){ zb&I~cAi?@h!tQ>cy=sm;7v9`m;^>&(;r4TXPa^xQRfd^m6oZaDmQ7x*l+oTP82R z{JWK_Rw*heuU)@>gQ}W_27JqpI=b8S^o@3!m|E;YVy$-D+3(qh+wbCb*u&GuHy|MR z7?Bhf9u-52kN^4fnX`N3T1U_VJ8s zAIt9HT~;7JR+v89)Nj%L(MO@H+h_d?dXY`Ek43w5_|$>USNOuL3jF#qJfe{K@9ENY zeij|C98bE2J8usvE%*H#$B%oZ`F7AeDZcx#KgsIQ6HaQf=c#ckx4NUnVOhrZ{%)-I z_se~2h)v4PN>Rg#Nuh7`?>x&@c|W_s)?hq*>Y6Px${Rubmzf8mk!kiJ+p<}>@$FoW zLRX*H?nhj`H@F#(zLP=R1Kn--V_6kWyPH`N9eB)V%-bS9@)y>@#Eop$a(?Vbs|V4Z zfp?lwDz1Jy8TVq7L{*`8%<>jcV-NUqz6g`=jBOG9n(w-{72Gd2X|K#vAQrE)Q<7MI zcvh5ruPF3H-!eY(F3Ua0eKVIgiy(9EzH9gITEN2%i~# zvn8QJozhSnTm0ZC&WRrUBz}r?knK zOFByxI3tJSl3((X_gEW`2swM1X2@|9_nH0Faib4dabq1O#@EE%$9|#kudre_$0L!_ zH=KDzOTK{LU6hf0`p7Wn!_XP70&vb(v=Bmdw24a4+zf3Nkciecff7u@0pvUC}xScg6ChpzD zlK!4(9}h&6B7g1xqIB3~A;dACDi~q!8p9g59}lA6 z>#F)I_LDsO+K!H23RWtkTE3))l8kb7belY#UqXPXXw;fn zMOlb6DQedQ2`*c7Vter3S8I1M*_6Vxk5w#@QXqvW9uV$2Ry8Kz1 zJ5_5cejxpBM<2eYraC2mm@||V{?kLUvJ-Wje4?+mSIDVmQ_g`Tw_JtWq%#A112H;j z6|>3mZ9RQk44Aoo+cJFlj*-1TN{p??TOoq1jjP9;`LQ;5Qlc(w z%3ETucoUr#7cXbTY|f&;mIqv|DY;_eQx0Rcg+vYVTOIBP7d{fD3S)YEI>ZDP{pGbB z2Bq5Ik!eY(_8(Cv@vDOTOrH-E5~u?2E{}j8au`VmCHA~#UX^K1GwC&(;!c*P?`Y!f z_|RIFpQ-(Ln4p5><}rpjr&hDkYive17#%`FGjHvmIrNvGvKi}Ib)`3F4PeV}gDg{C z@WV(X_x>5G*Tu3Xp5q=Prsn3m;a>29(?AFCo^2voY|9WNABGFJbMv%`lU{ID$1b&& zu#=Z>u_-(q47k(+E3VRg^wa%h>7Cn6VJGpMfnJ~j3)gznROc$>qMtUjR#7s|0SAu| zuv3Jb{49p$6B1+J+^3RU#W~|g(z5ycGeSXU&B4T;CKamSMHA2XPZGnS1N0FzIkVYA zl4S`r&NMdSF$Q_IK9WhTOfm`P|J_0bADAZ`M}PIJ0$G z`%%VkgA3xgBEJ@gTZgAR8U_hJMSGp5k48+E%@$HtNLz;o2}8B_f3J(IrgeTGqok(d z6k6wc5iTc{(IVz3%N%Yw4{esPn;Up5V!TphvDLjD0e&^!=+M40TK!ZA@5CW=@nAIRi7cSWc_G_UQjX{tl zG(rS8P=|)f_V=rCqjge&>MdwdHK3nfmG+SyOzfb?XC%=_l?MpV`E`*~P^+M19-o)6 zf1E??%pq&Q;;<67CZDGOlIp?>Vgz;~oJ9&G|4L^LP-Ha{EpQUy3j15;XR(z6ajb^g zIYuR@hkz7-Qsh;TrbJMB6(mImOma1J)CG>@Kp!M?344l$*Oah_AluMJQ#aI-wi3}# zIm@6WZE+G3riXfFF{{_(*{4zrp=B2@%I} zA2I&R{QdMR{_6jgfnuv;sIt-i&xF3I`OW;mDMP02*ksur5pM5f+2gU^*L#+S%h^+K zBxC6d(@dwQa~ZBb+B&Y2NEo38j?k!aq7aJdNRpgGG7< z>M%VMNmh1k?v$zFR#ljuxg<;VJR?g}>e&1T&+!7CZ9hoRX{>8qA5v0Fa@`C+94*o# z&C&3q<)y#g9U?S3cEYiaPJyB)tXbvjk4#8fY}u2r=OKyNYO9i*ZHpiX|DPKqs*k2yYGdP zrh%jyAnA3G%hE*B&`JXdD~^d$gCxI$WUo5ao@RkG9b90pgLhU9Bou(i?Y1#q*^(Q8 zq5?+o29y;@b-w{jv8FV!dMC*9Aw-lq+z#^0x3ulb_5qG2)4&l4v={;j`WJxt0km=# n15B5(M8_dh`|l58RO_0q;w3BE@~hylo}7dI0lN|#|HS_Q!v$$R literal 0 HcmV?d00001 diff --git a/template/client/store/index.js b/template/client/store/index.js new file mode 100644 index 0000000..5a9458d --- /dev/null +++ b/template/client/store/index.js @@ -0,0 +1,21 @@ +import Vue from 'vue'; +import Vuex from 'vuex'; + +import auth from './modules/auth'; + +Vue.use(Vuex); + +export default new Vuex.Store({ + state: { + breadcrumb: [{ + text: 'Home', + to: '/dashboard', + }, { + text: 'Dashboard', + active: true, + }], + }, + modules: { + auth, // auth namespaced + }, +}); diff --git a/template/client/store/modules/auth/actions.js b/template/client/store/modules/auth/actions.js new file mode 100644 index 0000000..f0ad3fb --- /dev/null +++ b/template/client/store/modules/auth/actions.js @@ -0,0 +1,78 @@ +import loopback from '@/services/loopback'; +import router from '@/router.js'; + +/** + * Sign-in account with email and password (stores in state.account) + * @param {Function} commit commit mutations function + * @param {String} email user email + * @param {String} password user password + * @return {Promise} promise of logged user + */ +export function signIn({commit, dispatch, state}, {email, password}) { + return loopback + .post('/Accounts/login', { + email, + password, + }) + .then(token => { + commit('setAccessToken', token); + router.push({name: 'agenda'}); + return dispatch('loadAccount', state.access_token.userId); + }); +} + +/** + * Sign-out current logged-in account + * (assumes that current state.account is not null) + * @param {Function} commit commit mutations function + * @return {Promise} promise of the logout + */ +export function signOut({commit}) { + return loopback + .post('/Accounts/logout') + .then(() => { + commit('setAccessToken', null); + router.push({name: 'login'}); + }); +} + +/** + * Load an account by userId in state.account + * @param {Function} commit commit mutations function + * @param {Number} userId account id + * @return {Promise} promise of the account + */ +export function loadAccount({commit}, userId) { + return loopback + .get('/Accounts/' + userId) + .then(acc => commit('setAccount', acc)); +} + +/** + * Reset the password of the current logged-in account + * (assumes that current state.account is not null) + * (assumes that current state.access_token is not null) + * @param {Function} commit commit mutations function + * @param {Object} state Vuex state + * @param {String} oldPassword old password + * @param {String} newPassword new password + * @return {Promise} promise of the password reset + */ +export function resetPassword({commit, state}, {oldPassword, newPassword}) { + return loopback + .post( + '/Accounts/change-password', + {oldPassword, newPassword} + ); +} + +/** + * Send a email to the account user to reset the password + * @param {Function} commit commit mutations function + * @param {String} email user email + * @return {Promise} promise of the sent email + */ +export function rememberPassword({commit}, email) { + return loopback + .post('/Accounts/reset', {email}); +} diff --git a/template/client/store/modules/auth/index.js b/template/client/store/modules/auth/index.js new file mode 100644 index 0000000..6b6c43b --- /dev/null +++ b/template/client/store/modules/auth/index.js @@ -0,0 +1,13 @@ +import * as actions from './actions.js'; +import * as mutations from './mutations.js'; + +export default { + namespaced: true, + state: { + // eslint-disable-next-line camelcase + access_token: null, + account: null, + }, + actions, + mutations, +}; diff --git a/template/client/store/modules/auth/mutations.js b/template/client/store/modules/auth/mutations.js new file mode 100644 index 0000000..3004163 --- /dev/null +++ b/template/client/store/modules/auth/mutations.js @@ -0,0 +1,15 @@ +import loopback from '@/services/loopback'; + +export function setAccessToken(state, token) { + // eslint-disable-next-line camelcase + state.access_token = token; + if (state.access_token !== null) { + loopback.setToken(state.access_token.id); + } else { + loopback.removeToken(); + } +} + +export function setAccount(state, account) { + state.account = account; +} diff --git a/template/client/style/_spinner.scss b/template/client/style/_spinner.scss new file mode 100644 index 0000000..3fda93b --- /dev/null +++ b/template/client/style/_spinner.scss @@ -0,0 +1,22 @@ +// Loading Spinner +.fa.fa-spinner { + -webkit-animation-name: rotate; + animation-name: rotate; + -webkit-animation-duration: 2s; + animation-duration: 2s; + -webkit-animation-iteration-count: infinite; + animation-iteration-count: infinite; + -webkit-animation-timing-function: ease-in-out; + animation-timing-function: ease-in-out; + } + + @-webkit-keyframes rotate { + from {-webkit-transform: rotate(0deg);transform: rotate(0deg);} + to {-webkit-transform: rotate(360deg);transform: rotate(360deg);} + } + + @keyframes rotate { + from {-webkit-transform: rotate(0deg);transform: rotate(0deg);} + to {-webkit-transform: rotate(360deg);transform: rotate(360deg);} + } + \ No newline at end of file diff --git a/template/client/style/app.scss b/template/client/style/app.scss new file mode 100644 index 0000000..a3459f8 --- /dev/null +++ b/template/client/style/app.scss @@ -0,0 +1,16 @@ +@import "global"; +@import "bootstrap/scss/bootstrap"; + +// Customize Bootstrap Components Style +// (copy from node_modules/bootstrap/scss folder +// @import "_breadcrumb"; +// @import "_buttons"; +// @import "_dropdown"; +// @import "_forms"; +// @import "_fullcalendar"; +// @import "_modal"; +@import "_spinner"; +// @import "_tables"; + +$fa-font-path: "../static/fonts"; +@import "font-awesome/scss/font-awesome"; diff --git a/template/client/style/global.scss b/template/client/style/global.scss new file mode 100644 index 0000000..6bd5935 --- /dev/null +++ b/template/client/style/global.scss @@ -0,0 +1,54 @@ +// CAUTION: This file will be duplicated in every componentscss style scope +//; HINT: Use this for global variables and mixins +// +// +// Color system +//; +// $primary: #5478c0; +// $secondary: #7c8386; +// $success: #44b6ae; +// $info: #444b4e; +// $warning: #ffc85f; +// $danger: #fc5e66; +// $light: #e9edef; +// $light2: #a2a6a8; +// $dark: #1e2123; +// $blue: #5478c0; +// $purple: #944fc0; + +// Fonts +// $font-size-base: .875rem; +// $font-weight-bold: 700; +// $font-family-sans-serif: "OpenSans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; +// $font-family-monospace: "SFMono-Regular", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + +// Links +// $link-color: $secondary; + +// Components +// $border-radius: 0px; +// $border-radius-lg: 0px; +// $border-radius-sm: 0px; + +// Buttons +// $btn-border-radius: 1.250em; +// $btn-border-radius-lg: 1.250em; +// $btn-border-radius-sm: 1.250em; +// $btn-font-weight: $font-weight-bold; + +// Forms +// $input-color: $secondary; +// $input-border-color: $light; + +// Breadcrumbs +// $breadcrumb-bg: transparent; +// $breadcrumb-divider-color: $light2; +// $breadcrumb-active-color: $light2; + +// Body +// $body-color: $secondary; + +// Import Global Bootstrap Variables +// (the previous variables will not be overitten) +@import "bootstrap/scss/_functions"; +@import "bootstrap/scss/_variables"; diff --git a/template/client/view/Dashboard.vue b/template/client/view/Dashboard.vue new file mode 100644 index 0000000..69ef750 --- /dev/null +++ b/template/client/view/Dashboard.vue @@ -0,0 +1,27 @@ + + + + + diff --git a/template/client/view/Login.vue b/template/client/view/Login.vue new file mode 100644 index 0000000..a77c56f --- /dev/null +++ b/template/client/view/Login.vue @@ -0,0 +1,186 @@ + + + + + diff --git a/template/client/view/Profile.vue b/template/client/view/Profile.vue new file mode 100644 index 0000000..ca0d724 --- /dev/null +++ b/template/client/view/Profile.vue @@ -0,0 +1,147 @@ + + + + + diff --git a/template/client/view/containers/HeaderContainer.vue b/template/client/view/containers/HeaderContainer.vue new file mode 100644 index 0000000..633ec8f --- /dev/null +++ b/template/client/view/containers/HeaderContainer.vue @@ -0,0 +1,45 @@ + + + + + diff --git a/template/client/view/mixins/notification.js b/template/client/view/mixins/notification.js new file mode 100644 index 0000000..f1fab89 --- /dev/null +++ b/template/client/view/mixins/notification.js @@ -0,0 +1,17 @@ +export default { + methods: { + notifySuccess(msg) { + // TODO: show success message + }, + notifyError(error) { + // TODO: show error message + throw error; + }, + notifyWhenSuccess(msg) { + return () => { + // TODO: show success message + }; + }, + }, + }; + \ No newline at end of file diff --git a/template/gulp-tasks/build.js b/template/gulp-tasks/build.js index cdd0867..69e8c61 100644 --- a/template/gulp-tasks/build.js +++ b/template/gulp-tasks/build.js @@ -9,14 +9,40 @@ import modulesify from 'css-modulesify'; import source from 'vinyl-source-stream'; import buffer from 'vinyl-buffer'; import {dirs} from './config.js'; +import {customSass} from './compilers.js'; + +gulp.task('build:test', () => { + return gulp.src([ + path.resolve(dirs.test, '**/*.test.js'), + path.resolve(dirs.test, 'config.js'), + ]) + .pipe(sourcemaps.init()) + .pipe(babel()) + .pipe(sourcemaps.write()) + .pipe(gulp.dest(dirs.buildTest)); +}); gulp.task('build:client', ['copy:client'], () => { + vueify.compiler.applyConfig({ + sass: { + includePaths: [ + dirs.modules, + ], + }, + customCompilers: { + scss: customSass, + }, + }); + let b = browserify({ entries: path.resolve(dirs.srcClient, 'main.js'), debug: true, - transform: [babelify, vueify], }); + b.transform(vueify); + + b.transform(babelify); + b.plugin(modulesify, { output: path.resolve( dirs.buildClient, @@ -59,6 +85,7 @@ gulp.task('build:server', () => { }); gulp.task('build', [ + 'build:test', 'build:client', 'build:common', 'build:server', diff --git a/template/gulp-tasks/config.js b/template/gulp-tasks/config.js index 2edbb27..9d90196 100644 --- a/template/gulp-tasks/config.js +++ b/template/gulp-tasks/config.js @@ -2,6 +2,7 @@ import path from 'path'; export const dirs = {}; dirs.root = path.resolve(__dirname, '../'); +dirs.modules = path.resolve(dirs.root, 'node_modules'); dirs.build = path.resolve(dirs.root, 'build'); dirs.buildTest = path.resolve(dirs.root, dirs.build, 'test'); dirs.buildClient = path.resolve(dirs.root, dirs.build, 'client'); diff --git a/template/gulp-tasks/copy.js b/template/gulp-tasks/copy.js index 7b354aa..ac1b631 100644 --- a/template/gulp-tasks/copy.js +++ b/template/gulp-tasks/copy.js @@ -2,7 +2,12 @@ import gulp from 'gulp'; import path from 'path'; import {dirs} from './config.js'; -gulp.task('copy:client', function() { +gulp.task('copy:client:fa', function() { + return gulp.src(path.resolve(dirs.modules, 'font-awesome/fonts/*')) + .pipe(gulp.dest(path.resolve(dirs.buildClient, 'static/fonts'))); +}); + +gulp.task('copy:client', ['copy:client:fa'], function() { return gulp.src(dirs.srcClient + '/**/!(*.vue|*.js)') .pipe(gulp.dest(path.resolve(dirs.buildClient))); }); diff --git a/template/gulp-tasks/serve.js b/template/gulp-tasks/serve.js index dbc3fda..b3f4275 100644 --- a/template/gulp-tasks/serve.js +++ b/template/gulp-tasks/serve.js @@ -1,6 +1,7 @@ import gulp from 'gulp'; import gutil from 'gulp-util'; import connect from 'gulp-connect'; +import historyApiFallback from 'connect-history-api-fallback'; import {dirs} from './config.js'; import server from '../server/server.js'; @@ -13,6 +14,7 @@ gulp.task('reload:server', ['build:server'], () => { gulp.task('watch:server', () => { gulp.watch([ dirs.srcServer + '/**/*.js', + dirs.srcServer + '/**/*.json', dirs.srcCommon + '/**/*.js', ], ['reload:server']); }); @@ -24,9 +26,8 @@ gulp.task('reload:client', ['build:client'], () => { gulp.task('watch:client', () => { gulp.watch([ - dirs.srcClient + '/**/*.js', + dirs.srcClient + '/**/*', dirs.srcCommon + '/**/*.js', - dirs.srcClient + '/**/*.vue', ], ['reload:client']); }); @@ -39,6 +40,9 @@ gulp.task('serve:client', ['build:client', 'watch:client'], () => { name: 'Client App', root: dirs.buildClient, livereload: true, + middleware: (connect, opt) => { + return [historyApiFallback()]; + }, }); }); diff --git a/template/package.json b/template/package.json index 164050f..deea102 100644 --- a/template/package.json +++ b/template/package.json @@ -11,18 +11,33 @@ "build": "gulp build", "test": "gulp test", "test:server": "gulp test:server", - "test:client": "gulp test:client" + "test:client": "gulp test:client", + "vulnerabilities": "nsp check" }, "dependencies": { "compression": "^1.0.3", "cors": "^2.5.2", "helmet": "^1.3.0", - "loopback": "^3.0.0", - "loopback-boot": "^2.6.5", + "loopback": "^3.16.2", + "loopback-boot": "^2.27.0", "loopback-component-explorer": "^4.0.0", "serve-favicon": "^2.0.1", "strong-error-handler": "^2.0.0", - "vue": "^2.4.1" + "vue": "^2.5.3"{{#extended}}, + "axios": "^0.17.1", + "babel-polyfill": "^6.26.0", + "bootstrap": "^4.0.0-beta.2", + "bootstrap-vue": "^1.2.0", + "font-awesome": "github:FortAwesome/Font-Awesome", + "install": "^0.10.1", + "lodash.defaultsdeep": "^4.6.0", + "loopback-ds-timestamp-mixin": "^3.4.1", + "vue-awesome": "^2.3.4", + "vue-router": "^3.0.1", + "vue-template-compiler": "^2.5.3", + "vuex": "^3.0.1", + "vuex-router-sync": "^5.0.0" + {{/extended}} }, "devDependencies": { "babel-core": "^6.25.0", diff --git a/template/server/boot/add-initial-data.js b/template/server/boot/add-initial-data.js new file mode 100644 index 0000000..72a46a5 --- /dev/null +++ b/template/server/boot/add-initial-data.js @@ -0,0 +1,34 @@ +// import initialProf from '../../server/initial-data/example1-teacher'; +// import initialClassroom from '../../server/initial-data/example1-classroom'; +// import initialStudent from '../../server/initial-data/example1-student'; + +/** + * Create the initial data of the system + */ +export default function addInitialData(server) { +// const Teacher = server.models.Teacher; +// const Classroom = server.models.Classroom; +// const Student = server.models.Student; + +// const createTeacher = () => { +// return Teacher.create(initialProf); +// }; +// const createStudent = teacher => { +// initialStudent.teacherId = teacher.id; +// return Student.create(initialStudent) +// .then(student => ({teacher, student})); +// }; +// const createClassroom = ({student, teacher}) => { +// initialClassroom.studentId = student.id; +// initialClassroom.teacherId = teacher.id; +// return Classroom.create(initialClassroom).then(classroom => ({ +// teacher, +// student, +// classroom, +// })); +// }; + +// return createTeacher() +// .then(createStudent) +// .then(createClassroom); +} diff --git a/template/server/boot/create-admin.js b/template/server/boot/create-admin.js new file mode 100644 index 0000000..6a7cd3b --- /dev/null +++ b/template/server/boot/create-admin.js @@ -0,0 +1,51 @@ +import initialAccount from '../initial-data/maintenance-account'; + +export const email = initialAccount.email; +export const password = initialAccount.password; + +/** + * Create the first admin user if there are not users in the system + */ +export default function createAdmin(server) { + const Account = server.models.Account; + const Role = server.models.Role; + const RoleMapping = server.models.RoleMapping; + + return Account + .find() + .then(accounts => { + if (accounts.length < 1) { + return Account.create({email, password}); + } + }) + .then(account => { + if (account) { + return Role + .find({name: 'admin'}) + .then(roles => { + if (roles.length < 1) { + return Role.create({ + name: 'admin', + }); + } + + return roles[0]; + }) + .then(role => { + // resolve with a payload + return {account, role}; + }); + } + }) + .then(payload => { // get account and role from payload + if (payload && payload.account && payload.role) { + return payload.role.principals.create({ + principalType: RoleMapping.USER, + principalId: payload.account.id, + }).then(principal => { + payload.principal = principal; + return payload; + }); + } + }); +} diff --git a/template/server/datasources.json b/template/server/datasources.json index d6caf56..652c20c 100644 --- a/template/server/datasources.json +++ b/template/server/datasources.json @@ -2,5 +2,25 @@ "db": { "name": "db", "connector": "memory" + }, + {{#extended}} + "transient": { + "name": "transient", + "connector": "transient" + }, + "email": { + "name": "email", + "connector": "mail", + "transports": [{ + "type": "smtp", + "host": "mywebhost.com", + "secure": true, + "port": 465, + "auth": { + "user": "noreply@mydomain.com", + "pass": "h4ckme" + } + }] } + {{/extended}} } diff --git a/template/server/initial-data/maintenance-account.json b/template/server/initial-data/maintenance-account.json new file mode 100644 index 0000000..80e5123 --- /dev/null +++ b/template/server/initial-data/maintenance-account.json @@ -0,0 +1,4 @@ +{ + "email": "admin@mydomain.com", + "password": "h4ckme" +} diff --git a/template/server/model-config.json b/template/server/model-config.json index a432664..25ed914 100644 --- a/template/server/model-config.json +++ b/template/server/model-config.json @@ -9,16 +9,27 @@ "mixins": [ "loopback/common/mixins", "loopback/server/mixins", + {{#extended}} + "../node_modules/loopback-ds-timestamp-mixin", + {{/extended}} "../common/mixins", "./mixins" ] }, "User": { - "dataSource": "db" + "dataSource": "db"{{#extended}}, + "public": false{{/extended}} }, "AccessToken": { "dataSource": "db", - "public": false + "public": false{{#extended}}, + "relations": { + "account": { + "type": "belongsTo", + "model": "Account", + "foreignKey": "userId" + } + }{{/extended}} }, "ACL": { "dataSource": "db", @@ -29,13 +40,37 @@ "public": false, "options": { "strictObjectIDCoercion": true - } + }{{#extended}}, + "relations": { + "account": { + "type": "belongsTo", + "model": "Account", + "foreignKey": "userId" + }, + "role": { + "type": "belongsTo", + "model": "Role", + "foreignKey": "roleId" + } + }{{/extended}} }, "Role": { "dataSource": "db", - "public": false + "public": false{{#extended}}, + "relations": { + "account": { + "type": "hasMany", + "model": "Account", + "through": "RoleMapping", + "foreignKey": "roleId" + } + } + }, + "Account": { + "dataSource": "db", + "public": true }, - "Message": { - "dataSource": null + "Email": { + "dataSource": "email"{{/extended}} } } diff --git a/template/server/models/account.js b/template/server/models/account.js new file mode 100644 index 0000000..2ccef47 --- /dev/null +++ b/template/server/models/account.js @@ -0,0 +1,20 @@ +import {host} from '../config.json'; + +export default function(Account) { + Account.on('resetPasswordRequest', function(info) { + var url = 'https://' + host + '/#!/profile'; + var html = 'Hello!

Click here to create a new password.' + + '

If you have not requested a password change,' + + 'please ignore this email.

Webmaster'; + Account.app.models.Email.send({ + to: info.email, + from: '{{#name}} ', + subject: '[{{name}}] Create a new password', + html: html, + }, function(err) { + if (err) return console.log(err); + console.log('> sending password reset email to:', info.email); + }); + }); +}; diff --git a/template/server/models/account.json b/template/server/models/account.json new file mode 100644 index 0000000..110156d --- /dev/null +++ b/template/server/models/account.json @@ -0,0 +1,47 @@ +{ + "name": "Account", + "plural": "Accounts", + "base": "User", + "idInjection": true, + "options": { + "validateUpsert": true + }, + "mixins": { + "TimeStamp": { + "required" : false, + "validateUpsert": true, + "silenceWarnings": false + } + }, + "properties": {}, + "validations": [], + "relations": { + "roleMapping": { + "type": "hasMany", + "model": "RoleMapping", + "foreignKey": "principalId" + }, + "role": { + "type": "hasMany", + "model": "Role", + "through": "RoleMapping", + "foreignKey": "principalId" + } + }, + "acls": [{ + "accessType": "*", + "principalType": "ROLE", + "principalId": "admin", + "permission": "ALLOW" + }, + { + "accessType": "EXECUTE", + "principalType": "ROLE", + "principalId": "$owner", + "permission": "ALLOW", + "property": "update" + } + ], + "methods": {} + } + \ No newline at end of file diff --git a/template/test/server/account.test.js b/template/test/server/account.test.js new file mode 100644 index 0000000..76622c3 --- /dev/null +++ b/template/test/server/account.test.js @@ -0,0 +1,29 @@ +import app from '../../server/server.js'; + +describe('Account', () => { + const Account = app.models.Account; + const email = '936ue5+4bnywbeje42pw@sharklasers.com'; + let testAccount; + + beforeEach(() => { + let appStarted = new Promise(res => app.addListener('started', res)); + app.start(); + return appStarted.then(() => { + return Account + .create({email, password: 'IuhEW7HI#&HUH3'}) + .then(acc => testAccount = acc); + }); + }); + + afterEach(() => { + Account.destroyById(testAccount.id); + app.close(); + }); + + it('should send reset email to test user', () => { + return request(app) + .post('/api/Accounts/reset') + .send({email}) + .then(res => expect(res).to.have.status(204)); + }).slow(5000).timeout(30000); +}); diff --git a/template/test/server/boot.test.js b/template/test/server/boot.test.js index 7d9bb16..3bf1891 100644 --- a/template/test/server/boot.test.js +++ b/template/test/server/boot.test.js @@ -1,24 +1,67 @@ import loopback from 'loopback'; +{{#extended}} +import boot from 'loopback-boot'; +import path from 'path'; + import root from '../../server/boot/root.js'; import auth from '../../server/boot/authentication.js'; +import createAdmin from '../../server/boot/create-admin.js'; +import initialAccount from '../../server/initial-data/manutencao-account'; +import initialProf from '../../server/initial-data/example1-professor'; +import initialAula from '../../server/initial-data/example1-aula'; +import initialAluno from '../../server/initial-data/example1-aluno'; +{{/extended}} describe('boot process', () => { - let server = loopback(); + {{#extended}} + const options = { + appRootDir: path.resolve(__dirname, '../../server'), + }; + let server; - it('should return server status by root.js', (done) => { - root(server); + beforeEach(done => { + server = loopback(); + boot(server, options, done); + }); - let conn = server.listen(8000, () => { - request(server).get('/').then((res) => { - expect(res).to.have.status(200); - expect(res.body).to.have.property('started'); - expect(res.body).to.have.property('uptime'); - conn.close(done); + afterEach(done => { + // Clear memory database + server.dataSources.db.automigrate(done); + }); + {{/extended}} + describe('root.js', () => { + it('should return server status by root.js', (done) => { + let conn = server.listen(8000, () => { + request(server).get('/').then((res) => { + expect(res).to.have.status(200); + expect(res.body).to.have.property('started'); + expect(res.body).to.have.property('uptime'); + conn.close(done); + }); }); }); }); + {{extended}} + describe('authentication.js', () => { + it('should enable authentication by authentication.js', () => { + expect(server.isAuthEnabled).to.equal(true); + }); + }); + + describe('email configuration', () => { + it('should have Email model', () => { + expect(server.models).has.property('Email'); + }); - it.skip('should enable authentication by authentication.js', () => { - auth(server); + it('Email model should send email', (done) => { + server.models.Email.send({ + from: 'noreply@mydomain.com', + to: '92y0zm+7xhtk2ni75mas@grr.la', + subject: 'Testing email', + text: 'Testing email text', + html: 'Testing email text', + }, done); + }).slow(5000).timeout(30000); }); + {{/extended}} }); From 5d1e022814274328492438fd39c6dd6bc59843f8 Mon Sep 17 00:00:00 2001 From: Walker Leite Date: Tue, 12 Dec 2017 10:15:55 -0200 Subject: [PATCH 03/25] fix(template): fix template errors --- template/client/main.js | 2 +- template/server/models/account.js | 2 +- template/test/server/boot.test.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/template/client/main.js b/template/client/main.js index 5fefacb..cbce78b 100644 --- a/template/client/main.js +++ b/template/client/main.js @@ -27,7 +27,7 @@ import store from './store'; sync(store, router); {{/extended}} -{{unless extended}} +{{#unless extended}} import './static/main.css'; {{/unless}} diff --git a/template/server/models/account.js b/template/server/models/account.js index 2ccef47..cfb4f2d 100644 --- a/template/server/models/account.js +++ b/template/server/models/account.js @@ -9,7 +9,7 @@ export default function(Account) { 'please ignore this email.

Webmaster'; Account.app.models.Email.send({ to: info.email, - from: '{{#name}} ', + from: '{{name}} ', subject: '[{{name}}] Create a new password', html: html, }, function(err) { diff --git a/template/test/server/boot.test.js b/template/test/server/boot.test.js index 3bf1891..5a9a995 100644 --- a/template/test/server/boot.test.js +++ b/template/test/server/boot.test.js @@ -41,7 +41,7 @@ describe('boot process', () => { }); }); }); - {{extended}} + {{#extended}} describe('authentication.js', () => { it('should enable authentication by authentication.js', () => { expect(server.isAuthEnabled).to.equal(true); From de8864345213b273bd47f79d828cdaf4d6d1415c Mon Sep 17 00:00:00 2001 From: Walker Leite Date: Tue, 12 Dec 2017 11:14:43 -0200 Subject: [PATCH 04/25] fix(template): add missing dependencies and missing compilers file --- template/gulp-tasks/compilers.js | 32 ++++++++++++++++++++++++++++++++ template/package.json | 10 ++++++++-- 2 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 template/gulp-tasks/compilers.js diff --git a/template/gulp-tasks/compilers.js b/template/gulp-tasks/compilers.js new file mode 100644 index 0000000..2c4b483 --- /dev/null +++ b/template/gulp-tasks/compilers.js @@ -0,0 +1,32 @@ +import path from 'path'; +import sass from 'vueify/lib/compilers/sass'; +import {dirs} from './config.js'; + +function replaceCuringas(content) { + // FIXME: Not working + const replacer = '@import "' + dirs.srcClient + '/$2";'; + + let replaced = content.replace(/@import.+(\@)(.*?)[\"\']/, replacer); + replaced = content.replace(/@import.+(\~)(.*?)[\"\']/, replacer); + return replaced; +} + +export function customSass(content, callback, compiler, filePath) { + const relativePath = path.relative( + path.dirname(filePath), + path.resolve(dirs.srcClient, 'style/global.scss') + ); + + // Global SCSS + // + content = '@import "' + relativePath + '";' + content; + + content = replaceCuringas(content); + + sass( + content, + callback, + compiler, + filePath + ); +} diff --git a/template/package.json b/template/package.json index deea102..4d4962f 100644 --- a/template/package.json +++ b/template/package.json @@ -41,7 +41,6 @@ }, "devDependencies": { "babel-core": "^6.25.0", - "babel-polyfill": "^6.26.0", "babel-preset-es2015": "^6.24.1", "babelify": "^7.3.0", "browserify": "^14.4.0", @@ -66,7 +65,14 @@ "sinon": "^2.3.8", "vinyl-buffer": "^1.0.0", "vinyl-source-stream": "^1.1.0", - "vueify": "^9.4.1" + "vueify": "^9.4.1", + "yargs": "^10.0.3", + "babel-plugin-root-import": "^5.1.0", + "connect-history-api-fallback": "^1.5.0", + "node-sass": "^4.6.0", + "nsp": "^2.1.0", + "gulp-util": "^3.0.8", + "babel-register": "^6.26.0" }, "repository": { "type": "", From b02518f327edfec56069ec552ebb897723da5950 Mon Sep 17 00:00:00 2001 From: Walker Leite Date: Tue, 12 Dec 2017 11:20:15 -0200 Subject: [PATCH 05/25] fix(template): Fix wrong comonente filename --- .../components/HelloWorld/{hello-world.vue => HelloWorld.vue} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename template/client/components/HelloWorld/{hello-world.vue => HelloWorld.vue} (100%) diff --git a/template/client/components/HelloWorld/hello-world.vue b/template/client/components/HelloWorld/HelloWorld.vue similarity index 100% rename from template/client/components/HelloWorld/hello-world.vue rename to template/client/components/HelloWorld/HelloWorld.vue From b6711abed10f14d69f47c800a786cf1bb1104386 Mon Sep 17 00:00:00 2001 From: Walker Leite Date: Tue, 12 Dec 2017 11:21:49 -0200 Subject: [PATCH 06/25] fix(tempalte): fix hello world component --- template/client/components/HelloWorld/HelloWorld.vue | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/template/client/components/HelloWorld/HelloWorld.vue b/template/client/components/HelloWorld/HelloWorld.vue index 9065479..5906e7a 100644 --- a/template/client/components/HelloWorld/HelloWorld.vue +++ b/template/client/components/HelloWorld/HelloWorld.vue @@ -1,9 +1,11 @@ From 0942b36774243ffa838733f419789c787070dd6d Mon Sep 17 00:00:00 2001 From: Walker Leite Date: Tue, 12 Dec 2017 11:31:19 -0200 Subject: [PATCH 07/25] fix(template): fix wrong route and add create-admin test --- template/client/store/modules/auth/actions.js | 2 +- template/test/server/boot.test.js | 44 +++++++++++++++++-- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/template/client/store/modules/auth/actions.js b/template/client/store/modules/auth/actions.js index f0ad3fb..ee3f423 100644 --- a/template/client/store/modules/auth/actions.js +++ b/template/client/store/modules/auth/actions.js @@ -16,7 +16,7 @@ export function signIn({commit, dispatch, state}, {email, password}) { }) .then(token => { commit('setAccessToken', token); - router.push({name: 'agenda'}); + router.push({name: 'dashboard'}); return dispatch('loadAccount', state.access_token.userId); }); } diff --git a/template/test/server/boot.test.js b/template/test/server/boot.test.js index 5a9a995..dae1ebe 100644 --- a/template/test/server/boot.test.js +++ b/template/test/server/boot.test.js @@ -7,10 +7,8 @@ import root from '../../server/boot/root.js'; import auth from '../../server/boot/authentication.js'; import createAdmin from '../../server/boot/create-admin.js'; -import initialAccount from '../../server/initial-data/manutencao-account'; -import initialProf from '../../server/initial-data/example1-professor'; -import initialAula from '../../server/initial-data/example1-aula'; -import initialAluno from '../../server/initial-data/example1-aluno'; +import initialAccount from '../../server/initial-data/maintenance-account'; + {{/extended}} describe('boot process', () => { {{#extended}} @@ -63,5 +61,43 @@ describe('boot process', () => { }, done); }).slow(5000).timeout(30000); }); + + describe('create-admin.js', () => { + it('should have Account model', () => { + expect(server.models).has.property('Account'); + }); + + it('should create a default admin user', () => { + return server.models.Account.find().then(res => { + expect(res).to.have.lengthOf(1); + expect(res[0]).to.have.property('createdAt'); + expect(res[0]).to.have.property('updatedAt'); + expect(res[0].id).to.equal(1); + expect(res[0].email).to.equal(initialAccount.email); + expect(res[0].password).to.be.an('string'); + }); + }); + + it('should create a default admin role', () => { + return server.models.Role.find().then(res => { + expect(res).to.have.lengthOf(1); + expect(res[0]).to.have.property('created'); + expect(res[0]).to.have.property('modified'); + expect(res[0].id).to.equal(1); + expect(res[0].name).to.equal('admin'); + }); + }); + + it('should create RoleMapping entry for admin', () => { + const RoleMapping = server.models.RoleMapping; + return RoleMapping.find().then(res => { + expect(res).to.have.lengthOf(1); + expect(res[0].id).to.equal(1); + expect(res[0].roleId).to.equal(1); + expect(res[0].principalId).to.equal(1); + expect(res[0].principalType).to.equal(RoleMapping.USER); + }); + }); + }); {{/extended}} }); From 0f31e9380779eea7b82f1c2d889f0cb238a93e63 Mon Sep 17 00:00:00 2001 From: Walker Leite Date: Tue, 12 Dec 2017 12:33:05 -0200 Subject: [PATCH 08/25] fix(template): fix css and translate strings to english --- template/client/App.vue | 1 + template/client/style/app.scss | 6 ++++++ template/client/view/Login.vue | 18 ++++++++--------- template/client/view/Profile.vue | 18 ++++++++--------- .../view/containers/HeaderContainer.vue | 2 +- template/server/datasources.json | 20 ++++++++++--------- template/test/server/boot.test.js | 2 +- 7 files changed, 38 insertions(+), 29 deletions(-) diff --git a/template/client/App.vue b/template/client/App.vue index 0bd8e58..f5dfa61 100644 --- a/template/client/App.vue +++ b/template/client/App.vue @@ -17,4 +17,5 @@ export default { diff --git a/template/client/style/app.scss b/template/client/style/app.scss index a3459f8..e8e827f 100644 --- a/template/client/style/app.scss +++ b/template/client/style/app.scss @@ -14,3 +14,9 @@ $fa-font-path: "../static/fonts"; @import "font-awesome/scss/font-awesome"; + +#app { + width: 100vw; + height: 100vh; + background-color: darken($light, 40%); +} diff --git a/template/client/view/Login.vue b/template/client/view/Login.vue index a77c56f..9f0da17 100644 --- a/template/client/view/Login.vue +++ b/template/client/view/Login.vue @@ -13,16 +13,16 @@ class="form-control" id="email" aria-describedby="emailHelp" - placeholder="Digite seu e-mail" + placeholder="Insert your email" v-model="email" required>
- +
@@ -33,13 +33,13 @@ - Esqueceu sua senha? + Forgot your password? @@ -47,7 +47,7 @@ @@ -63,7 +63,7 @@ type="email" class="form-control" ref="recoverEmail" - placeholder="Digite o seu e-mail" + placeholder="Insert your email" v-model="recoverEmail" @keydown.enter="sendRecoverEmail" required/> @@ -73,7 +73,7 @@ - Um email foi enviado para você, por favor verifique a caixa de entrada + An email has been sent, please verify your mailbox @@ -141,7 +141,7 @@ export default { this.recoverError = err; }); } else { - this.recoverError = {message: 'Verifique o e-mail digitado e tente novamente'}; + this.recoverError = {message: 'Please, check the inserted email and try again'}; } } } diff --git a/template/client/view/Profile.vue b/template/client/view/Profile.vue index ca0d724..4277168 100644 --- a/template/client/view/Profile.vue +++ b/template/client/view/Profile.vue @@ -7,29 +7,29 @@
- +
- +
- +
@@ -41,13 +41,13 @@ class="btn btn-danger" @click="onCancel"> - CANCELAR + CANCEL
@@ -87,7 +87,7 @@ export default { if(this.passwordNew1 !== this.passwordNew2) { this.loading = false - this.error = new Error('As senhas não conferem, por favor tente novamente') + this.error = new Error('The password does not match, please try again') return; } diff --git a/template/client/view/containers/HeaderContainer.vue b/template/client/view/containers/HeaderContainer.vue index 633ec8f..37b05f0 100644 --- a/template/client/view/containers/HeaderContainer.vue +++ b/template/client/view/containers/HeaderContainer.vue @@ -9,7 +9,7 @@ - ALTERAR SENHA + CHANGE PASSWORD LOG OUT diff --git a/template/server/datasources.json b/template/server/datasources.json index 652c20c..a3db822 100644 --- a/template/server/datasources.json +++ b/template/server/datasources.json @@ -11,16 +11,18 @@ "email": { "name": "email", "connector": "mail", - "transports": [{ - "type": "smtp", - "host": "mywebhost.com", - "secure": true, - "port": 465, - "auth": { - "user": "noreply@mydomain.com", - "pass": "h4ckme" + "transports": [ + { + "type": "smtp", + "host": "smtp.mailtrap.io", + "secure": false, + "port": 2525, + "auth": { + "user": "bc6242e46c4abb", + "pass": "6dcc9b55c677be" + } } - }] + ] } {{/extended}} } diff --git a/template/test/server/boot.test.js b/template/test/server/boot.test.js index dae1ebe..87ec7d8 100644 --- a/template/test/server/boot.test.js +++ b/template/test/server/boot.test.js @@ -53,7 +53,7 @@ describe('boot process', () => { it('Email model should send email', (done) => { server.models.Email.send({ - from: 'noreply@mydomain.com', + from: 'noreply@fakeserver.mailtrap.io', to: '92y0zm+7xhtk2ni75mas@grr.la', subject: 'Testing email', text: 'Testing email text', From 92c255089a46d33b7255ce030b8ebc1502b43832 Mon Sep 17 00:00:00 2001 From: Walker Leite Date: Tue, 12 Dec 2017 12:40:33 -0200 Subject: [PATCH 09/25] fix(template): remove gitlab file --- template/.gitlab-ci.yml | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 template/.gitlab-ci.yml diff --git a/template/.gitlab-ci.yml b/template/.gitlab-ci.yml deleted file mode 100644 index 4cfe2bb..0000000 --- a/template/.gitlab-ci.yml +++ /dev/null @@ -1,24 +0,0 @@ -# Official framework image. Look for the different tagged releases at: -# https://hub.docker.com/r/library/node/tags/ -image: node:latest - -before_script: - - npm install - -# This folder is cached between builds -# http://docs.gitlab.com/ce/ci/yaml/README.html#cache -cache: - paths: - - node_modules/ - -test:lint: - script: - - npm run lint - -test:vulnerabilities: - script: - - npm run vulnerabilities - -test:node:latest: - script: - - npm run test From 3e9e45e2f9da62b66e9d6b084c42ff60bcbbd27b Mon Sep 17 00:00:00 2001 From: Walker Leite Date: Tue, 12 Dec 2017 12:57:30 -0200 Subject: [PATCH 10/25] fix(tests): add vuefy compiler to karma --- template/test/karma.conf.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/template/test/karma.conf.js b/template/test/karma.conf.js index 669b4a0..c3fe31a 100644 --- a/template/test/karma.conf.js +++ b/template/test/karma.conf.js @@ -1,7 +1,20 @@ import babelify from 'babelify'; import vueify from 'vueify'; +import { dirs } from '../gulp-tasks/config'; +import { customSass } from '../gulp-tasks/compilers.js'; export default (config) => { + vueify.compiler.applyConfig({ + sass: { + includePaths: [ + dirs.modules, + ], + }, + customCompilers: { + scss: customSass, + }, + }); + config.set({ browsers: ['Chrome'], frameworks: ['browserify', 'mocha', 'chai'], From a71e8a626ab9c51ad55af232f1a37d56c9c0d3a1 Mon Sep 17 00:00:00 2001 From: Walker Leite Date: Tue, 12 Dec 2017 13:12:40 -0200 Subject: [PATCH 11/25] feat(tests): rename and add more tests --- meta.js | 3 +++ template/client/main.js | 2 +- template/gulp-tasks/test.js | 4 ++-- template/test/client/{app.test.js => app.spec.js} | 0 template/test/client/components/HelloWorld.spec.js | 13 +++++++++++++ template/test/client/main.spec.js | 9 +++++++++ .../server/{account.test.js => account.spec.js} | 0 template/test/server/{boot.test.js => boot.spec.js} | 0 .../test/server/{server.test.js => server.spec.js} | 0 9 files changed, 28 insertions(+), 3 deletions(-) rename template/test/client/{app.test.js => app.spec.js} (100%) create mode 100644 template/test/client/components/HelloWorld.spec.js create mode 100644 template/test/client/main.spec.js rename template/test/server/{account.test.js => account.spec.js} (100%) rename template/test/server/{boot.test.js => boot.spec.js} (100%) rename template/test/server/{server.test.js => server.spec.js} (100%) diff --git a/meta.js b/meta.js index 9f689e4..3a82303 100644 --- a/meta.js +++ b/meta.js @@ -36,6 +36,9 @@ module.exports = { "server/boot/create-admin.js": "extended", "server/initial-data/**/*": "extended", "server/models/**/*": "extended", + "test/client/app.spec.js": "extended === false", + "test/client/main.spec.js": "extended", + "test/client/components/**/*": "extended", "test/server/account.test.js": "extended", }, "complete": function(data, {logger}) { diff --git a/template/client/main.js b/template/client/main.js index cbce78b..85ac67c 100644 --- a/template/client/main.js +++ b/template/client/main.js @@ -32,7 +32,7 @@ import './static/main.css'; {{/unless}} // Instance Application -new Vue({ +export default new Vue({ el: '#app', render: (r) => r(App), {{#extended}} diff --git a/template/gulp-tasks/test.js b/template/gulp-tasks/test.js index 706174f..af865ce 100644 --- a/template/gulp-tasks/test.js +++ b/template/gulp-tasks/test.js @@ -5,7 +5,7 @@ import path from 'path'; import {dirs} from './config.js'; gulp.task('test:server', () => { - return gulp.src(path.resolve(dirs.testServer, '**/*.test.js')) + return gulp.src(path.resolve(dirs.testServer, '**/*.spec.js')) .pipe(mocha({ compilers: 'js:babel-core/register', require: path.resolve(dirs.test, 'mocha.conf.js'), @@ -14,7 +14,7 @@ gulp.task('test:server', () => { gulp.task('test:client', (done) => { new Server({ - files: [path.resolve(dirs.testClient, '**/*.test.js')], + files: [path.resolve(dirs.testClient, '**/*.spec.js')], configFile: path.resolve(dirs.test, 'karma.conf.js'), singleRun: true, }, done).start(); diff --git a/template/test/client/app.test.js b/template/test/client/app.spec.js similarity index 100% rename from template/test/client/app.test.js rename to template/test/client/app.spec.js diff --git a/template/test/client/components/HelloWorld.spec.js b/template/test/client/components/HelloWorld.spec.js new file mode 100644 index 0000000..15eb99b --- /dev/null +++ b/template/test/client/components/HelloWorld.spec.js @@ -0,0 +1,13 @@ +import Vue from 'vue'; +import HelloWorld from '@/components/HelloWorld/HelloWorld.vue'; + +describe('HelloWorld.vue', () => { + const Constructor = Vue.extend(HelloWorld); + + it('should render correct content', () => { + const vm = new Constructor().$mount(); + return Vue.nextTick().then(() => { + expect(vm.$el.innerHTML).to.equal('Hello World! This content is restricted.'); + }); + }); +}); diff --git a/template/test/client/main.spec.js b/template/test/client/main.spec.js new file mode 100644 index 0000000..b976340 --- /dev/null +++ b/template/test/client/main.spec.js @@ -0,0 +1,9 @@ +import main from '@/main'; + +describe('main file', () => { + it('should render login view', () => { + return main.$nextTick().then(() => { + expect(main.$el.querySelector('.login-view')).to.not.equal(undefined); + }) + }) +}) diff --git a/template/test/server/account.test.js b/template/test/server/account.spec.js similarity index 100% rename from template/test/server/account.test.js rename to template/test/server/account.spec.js diff --git a/template/test/server/boot.test.js b/template/test/server/boot.spec.js similarity index 100% rename from template/test/server/boot.test.js rename to template/test/server/boot.spec.js diff --git a/template/test/server/server.test.js b/template/test/server/server.spec.js similarity index 100% rename from template/test/server/server.test.js rename to template/test/server/server.spec.js From 0ba8e18eb0943255b63888c3741b686ac485aadb Mon Sep 17 00:00:00 2001 From: Walker Leite Date: Tue, 12 Dec 2017 13:41:53 -0200 Subject: [PATCH 12/25] fix(tests): add karma modulesify settings --- template/package.json | 1 + template/test/karma.conf.js | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/template/package.json b/template/package.json index 4d4962f..6350ca2 100644 --- a/template/package.json +++ b/template/package.json @@ -63,6 +63,7 @@ "karma-chrome-launcher": "^2.2.0", "mocha": "^3.4.2", "sinon": "^2.3.8", + "tmp": "0.0.33", "vinyl-buffer": "^1.0.0", "vinyl-source-stream": "^1.1.0", "vueify": "^9.4.1", diff --git a/template/test/karma.conf.js b/template/test/karma.conf.js index c3fe31a..59ab1ed 100644 --- a/template/test/karma.conf.js +++ b/template/test/karma.conf.js @@ -1,8 +1,13 @@ import babelify from 'babelify'; import vueify from 'vueify'; +import modulesify from 'css-modulesify'; +import tmp from 'tmp'; import { dirs } from '../gulp-tasks/config'; import { customSass } from '../gulp-tasks/compilers.js'; +const cssBundleFile = tmp.fileSync(); +const cssStream = fs.createWriteStream(cssBundleFile.name); + export default (config) => { vueify.compiler.applyConfig({ sass: { @@ -21,9 +26,25 @@ export default (config) => { preprocessors: { '**/*.js': ['browserify'], }, + files: [ + cssBundleFile.name, + ], browserify: { + output: cssBundleFile.name, debug: true, transform: [babelify, vueify], + plugin: [[modulesify, { + global: true, + generateScopedName: function (name, filename) { + var matches = filename.match(/^\/node_modules/); + if (matches) return name; + if (process.env.NODE_ENV === 'production') { + return modulesify.generateShortName(name, filename); + } else { + return modulesify.generateLongName(name, filename); + } + }, + }]], }, }); }; From 2f0a2e528aa4e008bdf5bb6e2fcd74129a652acb Mon Sep 17 00:00:00 2001 From: Walker Leite Date: Tue, 12 Dec 2017 14:27:11 -0200 Subject: [PATCH 13/25] fix(tests): fix test errors --- meta.js | 2 -- template/test/client/app.spec.js | 32 ++++++++++++++++++- .../test/client/components/HelloWorld.spec.js | 2 +- template/test/client/main.spec.js | 9 ------ template/test/karma.conf.js | 1 - 5 files changed, 32 insertions(+), 14 deletions(-) delete mode 100644 template/test/client/main.spec.js diff --git a/meta.js b/meta.js index 3a82303..2d92712 100644 --- a/meta.js +++ b/meta.js @@ -36,8 +36,6 @@ module.exports = { "server/boot/create-admin.js": "extended", "server/initial-data/**/*": "extended", "server/models/**/*": "extended", - "test/client/app.spec.js": "extended === false", - "test/client/main.spec.js": "extended", "test/client/components/**/*": "extended", "test/server/account.test.js": "extended", }, diff --git a/template/test/client/app.spec.js b/template/test/client/app.spec.js index 774e8b2..84e484a 100644 --- a/template/test/client/app.spec.js +++ b/template/test/client/app.spec.js @@ -1,13 +1,43 @@ import Vue from 'vue'; -import App from '../../client/App.vue'; +import App from '@/App.vue'; +{{#extended}} + +// Replicates main.js behavior +import 'babel-polyfill'; +import Vue from 'vue'; +import { sync } from 'vuex-router-sync'; +import 'bootstrap-vue/dist/bootstrap-vue.css'; +import BootstrapVue from 'bootstrap-vue'; +import Icon from 'vue-awesome'; +import router from './router.js'; +import store from './store'; + +Vue.use(BootstrapVue); +Vue.component('icon', Icon); +sync(store, router); +{{/extended}} describe('App.vue', () => { const Constructor = Vue.extend(App); + {{#extended}} + it('should render login view', () => { + const vm = new Constructor({ + render: r => r(App), + router, + store, + }).$mount(); + + return main.$nextTick().then(() => { + expect(main.$el.querySelector('.login-view')).to.not.equal(undefined); + }) + }) + {{else}} it('should render correct content', () => { const vm = new Constructor().$mount(); return Vue.nextTick().then(() => { expect(vm.$el.innerHTML).to.equal('Hello World!'); }); }); + {{/extended}} }); diff --git a/template/test/client/components/HelloWorld.spec.js b/template/test/client/components/HelloWorld.spec.js index 15eb99b..e1e0cae 100644 --- a/template/test/client/components/HelloWorld.spec.js +++ b/template/test/client/components/HelloWorld.spec.js @@ -7,7 +7,7 @@ describe('HelloWorld.vue', () => { it('should render correct content', () => { const vm = new Constructor().$mount(); return Vue.nextTick().then(() => { - expect(vm.$el.innerHTML).to.equal('Hello World! This content is restricted.'); + expect(vm.$el.innerHTML).to.include('Hello World! This content is restricted.'); }); }); }); diff --git a/template/test/client/main.spec.js b/template/test/client/main.spec.js deleted file mode 100644 index b976340..0000000 --- a/template/test/client/main.spec.js +++ /dev/null @@ -1,9 +0,0 @@ -import main from '@/main'; - -describe('main file', () => { - it('should render login view', () => { - return main.$nextTick().then(() => { - expect(main.$el.querySelector('.login-view')).to.not.equal(undefined); - }) - }) -}) diff --git a/template/test/karma.conf.js b/template/test/karma.conf.js index 59ab1ed..364efbd 100644 --- a/template/test/karma.conf.js +++ b/template/test/karma.conf.js @@ -6,7 +6,6 @@ import { dirs } from '../gulp-tasks/config'; import { customSass } from '../gulp-tasks/compilers.js'; const cssBundleFile = tmp.fileSync(); -const cssStream = fs.createWriteStream(cssBundleFile.name); export default (config) => { vueify.compiler.applyConfig({ From d8ae843771512ead08b25fec5d9653e459fc7d32 Mon Sep 17 00:00:00 2001 From: Walker Leite Date: Tue, 12 Dec 2017 14:35:20 -0200 Subject: [PATCH 14/25] fix(tests): fix test errors --- template/test/client/app.spec.js | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/template/test/client/app.spec.js b/template/test/client/app.spec.js index 84e484a..a854c6a 100644 --- a/template/test/client/app.spec.js +++ b/template/test/client/app.spec.js @@ -4,13 +4,12 @@ import App from '@/App.vue'; // Replicates main.js behavior import 'babel-polyfill'; -import Vue from 'vue'; import { sync } from 'vuex-router-sync'; import 'bootstrap-vue/dist/bootstrap-vue.css'; import BootstrapVue from 'bootstrap-vue'; import Icon from 'vue-awesome'; -import router from './router.js'; -import store from './store'; +import router from '@/router.js'; +import store from '@/store'; Vue.use(BootstrapVue); Vue.component('icon', Icon); @@ -21,15 +20,15 @@ describe('App.vue', () => { const Constructor = Vue.extend(App); {{#extended}} - it('should render login view', () => { + it.skip('should render login view', () => { + // FIXME: Rendering test not working const vm = new Constructor({ - render: r => r(App), router, store, }).$mount(); - return main.$nextTick().then(() => { - expect(main.$el.querySelector('.login-view')).to.not.equal(undefined); + return vm.$nextTick().then(() => { + expect(vm.$el.querySelector('.login-view')).to.not.equal(undefined); }) }) {{else}} From 070b19e23f79e28ceaed5a2182f9f4194ee6bc51 Mon Sep 17 00:00:00 2001 From: Walker Leite Date: Wed, 13 Dec 2017 11:03:41 -0200 Subject: [PATCH 15/25] fix(tests): fix app test --- template/test/client/app.spec.js | 48 +++++++++++++++----------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/template/test/client/app.spec.js b/template/test/client/app.spec.js index a854c6a..5f5180f 100644 --- a/template/test/client/app.spec.js +++ b/template/test/client/app.spec.js @@ -1,36 +1,32 @@ import Vue from 'vue'; import App from '@/App.vue'; -{{#extended}} -// Replicates main.js behavior -import 'babel-polyfill'; -import { sync } from 'vuex-router-sync'; -import 'bootstrap-vue/dist/bootstrap-vue.css'; -import BootstrapVue from 'bootstrap-vue'; -import Icon from 'vue-awesome'; -import router from '@/router.js'; -import store from '@/store'; +describe('App.vue', () => { + let Constructor, vm; + {{#exnteded}} + const routerView = { + render: r => r('div', 'mocked component'), + }; + {{/extended}} -Vue.use(BootstrapVue); -Vue.component('icon', Icon); -sync(store, router); -{{/extended}} + beforeEach(done => { + Constructor = Vue.extend(App); + vm = new Constructor({ + mounted: () => done(), + {{#extended}} + components: { routerView }, + {{/extended}} + }); + vm.$mount(); + }); -describe('App.vue', () => { - const Constructor = Vue.extend(App); + afterEach(() => vm.$destroy()); {{#extended}} - it.skip('should render login view', () => { - // FIXME: Rendering test not working - const vm = new Constructor({ - router, - store, - }).$mount(); - - return vm.$nextTick().then(() => { - expect(vm.$el.querySelector('.login-view')).to.not.equal(undefined); - }) - }) + it('should render router component', () => { + expect(vm.$el.innerHTML).to.equal('mocked component'); + expect(vm.$el.getAttribute('id')).to.equal('app'); + }); {{else}} it('should render correct content', () => { const vm = new Constructor().$mount(); From d865b79fe6231ab42c5df20d616ba80079ae9dd0 Mon Sep 17 00:00:00 2001 From: Walker Leite Date: Wed, 13 Dec 2017 11:04:33 -0200 Subject: [PATCH 16/25] fix(test): fix lint error --- template/test/client/components/HelloWorld.spec.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/template/test/client/components/HelloWorld.spec.js b/template/test/client/components/HelloWorld.spec.js index e1e0cae..22c884f 100644 --- a/template/test/client/components/HelloWorld.spec.js +++ b/template/test/client/components/HelloWorld.spec.js @@ -7,7 +7,9 @@ describe('HelloWorld.vue', () => { it('should render correct content', () => { const vm = new Constructor().$mount(); return Vue.nextTick().then(() => { - expect(vm.$el.innerHTML).to.include('Hello World! This content is restricted.'); + expect(vm.$el.innerHTML).to.include( + 'Hello World! This content is restricted.' + ); }); }); }); From 8fa8ae3037a18627181f2fba451a4039fb19c2bc Mon Sep 17 00:00:00 2001 From: Walker Leite Date: Wed, 13 Dec 2017 11:05:41 -0200 Subject: [PATCH 17/25] fix(template): fix misspeling --- template/test/client/app.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/template/test/client/app.spec.js b/template/test/client/app.spec.js index 5f5180f..1b8aa8a 100644 --- a/template/test/client/app.spec.js +++ b/template/test/client/app.spec.js @@ -3,7 +3,7 @@ import App from '@/App.vue'; describe('App.vue', () => { let Constructor, vm; - {{#exnteded}} + {{#extended}} const routerView = { render: r => r('div', 'mocked component'), }; From 47475f15c01362d0a15b5dd83d2cc8b45a6169b1 Mon Sep 17 00:00:00 2001 From: Walker Leite Date: Wed, 13 Dec 2017 11:14:23 -0200 Subject: [PATCH 18/25] fix(lint): fix lint errors --- template/client/main.js | 1 - template/client/view/mixins/notification.js | 27 ++++++++++----------- template/gulp-tasks/build.js | 2 +- template/test/client/app.spec.js | 2 +- template/test/karma.conf.js | 6 ++--- 5 files changed, 18 insertions(+), 20 deletions(-) diff --git a/template/client/main.js b/template/client/main.js index 85ac67c..f8664a3 100644 --- a/template/client/main.js +++ b/template/client/main.js @@ -26,7 +26,6 @@ import store from './store'; // Add router state to store sync(store, router); {{/extended}} - {{#unless extended}} import './static/main.css'; {{/unless}} diff --git a/template/client/view/mixins/notification.js b/template/client/view/mixins/notification.js index f1fab89..a3b48e5 100644 --- a/template/client/view/mixins/notification.js +++ b/template/client/view/mixins/notification.js @@ -1,17 +1,16 @@ export default { - methods: { - notifySuccess(msg) { + methods: { + notifySuccess(msg) { + // TODO: show success message + }, + notifyError(error) { + // TODO: show error message + throw error; + }, + notifyWhenSuccess(msg) { + return () => { // TODO: show success message - }, - notifyError(error) { - // TODO: show error message - throw error; - }, - notifyWhenSuccess(msg) { - return () => { - // TODO: show success message - }; - }, + }; }, - }; - \ No newline at end of file + }, +}; diff --git a/template/gulp-tasks/build.js b/template/gulp-tasks/build.js index 69e8c61..4a8fe01 100644 --- a/template/gulp-tasks/build.js +++ b/template/gulp-tasks/build.js @@ -33,7 +33,7 @@ gulp.task('build:client', ['copy:client'], () => { scss: customSass, }, }); - + let b = browserify({ entries: path.resolve(dirs.srcClient, 'main.js'), debug: true, diff --git a/template/test/client/app.spec.js b/template/test/client/app.spec.js index 1b8aa8a..b131163 100644 --- a/template/test/client/app.spec.js +++ b/template/test/client/app.spec.js @@ -14,7 +14,7 @@ describe('App.vue', () => { vm = new Constructor({ mounted: () => done(), {{#extended}} - components: { routerView }, + components: {routerView}, {{/extended}} }); vm.$mount(); diff --git a/template/test/karma.conf.js b/template/test/karma.conf.js index 364efbd..37ccc6a 100644 --- a/template/test/karma.conf.js +++ b/template/test/karma.conf.js @@ -2,8 +2,8 @@ import babelify from 'babelify'; import vueify from 'vueify'; import modulesify from 'css-modulesify'; import tmp from 'tmp'; -import { dirs } from '../gulp-tasks/config'; -import { customSass } from '../gulp-tasks/compilers.js'; +import {dirs} from '../gulp-tasks/config'; +import {customSass} from '../gulp-tasks/compilers.js'; const cssBundleFile = tmp.fileSync(); @@ -34,7 +34,7 @@ export default (config) => { transform: [babelify, vueify], plugin: [[modulesify, { global: true, - generateScopedName: function (name, filename) { + generateScopedName: function(name, filename) { var matches = filename.match(/^\/node_modules/); if (matches) return name; if (process.env.NODE_ENV === 'production') { From f10fb192e727f4932dd9a6d0d49d27adbd0a4c4f Mon Sep 17 00:00:00 2001 From: Walker Leite Date: Wed, 13 Dec 2017 11:16:42 -0200 Subject: [PATCH 19/25] chore(npm): update package-lock --- package-lock.json | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index 977d22c..68754c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "vue-loopback", - "version": "1.0.0", + "version": "1.0.2", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1835,15 +1835,6 @@ "integrity": "sha1-5sgLYjEj19gM8TLOU480YokHJQI=", "dev": true }, - "string_decoder": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", - "dev": true, - "requires": { - "safe-buffer": "5.1.1" - } - }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", @@ -1854,6 +1845,15 @@ "strip-ansi": "4.0.0" } }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, "stringstream": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", From 18970968e6aeb2a021b2e0bff40b33b60835b524 Mon Sep 17 00:00:00 2001 From: Walker Leite Date: Wed, 13 Dec 2017 12:44:37 -0200 Subject: [PATCH 20/25] fix(test): add extended tests --- test.js | 39 +++++++++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 8 deletions(-) diff --git a/test.js b/test.js index 0050346..c688224 100644 --- a/test.js +++ b/test.js @@ -1,22 +1,38 @@ const {spawn} = require('child_process'); +const initQuestions = [ + {search: /^\?.+/, response: "\n"}, +]; + +const initNoExtQuestions = [ + { search: /^\? Add basic Login and Admin.+/, response: "No\n" }, + { search: /^\?.+/, response: "\n" }, +]; + const commands = [ {cmd: 'rm', args: ['-r', 'test-project'], ignoreErrors: true}, - {cmd: './node_modules/.bin/vue', args: ['init', '.', 'test-project'], yes: true}, + {cmd: './node_modules/.bin/vue', args: ['init', '.', 'test-project'], responses: initNoExtQuestions}, {cmd: 'npm', args: ['install'], cwd: 'test-project'}, {cmd: 'npm', args: ['run', 'lint'], cwd: 'test-project'}, {cmd: 'npm', args: ['run', 'test:server'], cwd: 'test-project'}, {cmd: 'npm', args: ['run', 'test:client'], cwd: 'test-project'}, {cmd: 'npm', args: ['run', 'build'], cwd: 'test-project'}, + {cmd: 'rm', args: ['-r', 'test-project'], ignoreErrors: true}, + { cmd: './node_modules/.bin/vue', args: ['init', '.', 'test-project'], responses: initNoExtQuestions}, + { cmd: 'npm', args: ['install'], cwd: 'test-project' }, + { cmd: 'npm', args: ['run', 'lint'], cwd: 'test-project' }, + { cmd: 'npm', args: ['run', 'test:server'], cwd: 'test-project' }, + { cmd: 'npm', args: ['run', 'test:client'], cwd: 'test-project' }, + { cmd: 'npm', args: ['run', 'build'], cwd: 'test-project' }, ]; function executeCommand(command, index) { return new Promise((resolve, reject) => { let cp = spawn(command.cmd, command.args, {cwd: command.cwd}); process.on('exit', cp.kill); - cp.stdout.setEncoding('utf-8'); cp.stdin.setEncoding('utf-8'); + cp.stderr.setEncoding('utf-8'); // Ignore pipe errors cp.stdin.on('error', () => {}); @@ -26,18 +42,25 @@ function executeCommand(command, index) { cp.stderr.pipe(process.stderr); let rejected = false; - if(command.yes) { - cp.stdout.on('data', function() { - if (!rejected) cp.stdin.write("\n"); - }); - } + if (!rejected && command.responses) { + const registerResponse = q => { + cp.stdout.on('data', output => { + if (q.search.test(output)) { + // console.log('sending', q); + cp.stdin.write(q.response); + } + }); + } + + command.responses.forEach(registerResponse); + } cp.once('error', code => { if (!rejected) { reject(code); rejected = true; } }); - + cp.once('exit', code => { if (code) { reject(code); From e9bb6de085a2b7915de054633af198b6821f30a0 Mon Sep 17 00:00:00 2001 From: Walker Leite Date: Wed, 13 Dec 2017 12:53:05 -0200 Subject: [PATCH 21/25] fix(tests): fix tests for no extended build --- template/server/datasources.json | 3 +-- template/test/server/boot.spec.js | 10 ++++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/template/server/datasources.json b/template/server/datasources.json index a3db822..36a55c1 100644 --- a/template/server/datasources.json +++ b/template/server/datasources.json @@ -2,8 +2,7 @@ "db": { "name": "db", "connector": "memory" - }, - {{#extended}} + }{{#extended}}, "transient": { "name": "transient", "connector": "transient" diff --git a/template/test/server/boot.spec.js b/template/test/server/boot.spec.js index 87ec7d8..1204c51 100644 --- a/template/test/server/boot.spec.js +++ b/template/test/server/boot.spec.js @@ -1,6 +1,6 @@ import loopback from 'loopback'; -{{#extended}} import boot from 'loopback-boot'; +{{#extended}} import path from 'path'; import root from '../../server/boot/root.js'; @@ -11,15 +11,17 @@ import initialAccount from '../../server/initial-data/maintenance-account'; {{/extended}} describe('boot process', () => { + let server; {{#extended}} const options = { appRootDir: path.resolve(__dirname, '../../server'), }; - let server; - - beforeEach(done => { + {{/extended}} + beforeEach({{#extended}}done{{/extended}} => { server = loopback(); + {{#extended}} boot(server, options, done); + {{/extended}} }); afterEach(done => { From f6b4a4a9775d6ece37349475738a0ff573180200 Mon Sep 17 00:00:00 2001 From: Walker Leite Date: Wed, 13 Dec 2017 15:06:23 -0200 Subject: [PATCH 22/25] fix(test): fix boot test --- template/test/server/boot.spec.js | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/template/test/server/boot.spec.js b/template/test/server/boot.spec.js index 1204c51..8c2fb93 100644 --- a/template/test/server/boot.spec.js +++ b/template/test/server/boot.spec.js @@ -1,34 +1,28 @@ import loopback from 'loopback'; import boot from 'loopback-boot'; -{{#extended}} import path from 'path'; - +{{#extended}} import root from '../../server/boot/root.js'; import auth from '../../server/boot/authentication.js'; import createAdmin from '../../server/boot/create-admin.js'; - import initialAccount from '../../server/initial-data/maintenance-account'; - {{/extended}} + describe('boot process', () => { let server; - {{#extended}} const options = { appRootDir: path.resolve(__dirname, '../../server'), }; - {{/extended}} - beforeEach({{#extended}}done{{/extended}} => { + beforeEach(done => { server = loopback(); - {{#extended}} boot(server, options, done); - {{/extended}} }); afterEach(done => { // Clear memory database server.dataSources.db.automigrate(done); }); - {{/extended}} + describe('root.js', () => { it('should return server status by root.js', (done) => { let conn = server.listen(8000, () => { From 131f2ec166a11c62f28729af881f21affa0cf9f3 Mon Sep 17 00:00:00 2001 From: Walker Leite Date: Wed, 13 Dec 2017 15:07:09 -0200 Subject: [PATCH 23/25] fix(meta): fix wrong path name --- meta.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meta.js b/meta.js index 2d92712..51e4f21 100644 --- a/meta.js +++ b/meta.js @@ -37,7 +37,7 @@ module.exports = { "server/initial-data/**/*": "extended", "server/models/**/*": "extended", "test/client/components/**/*": "extended", - "test/server/account.test.js": "extended", + "test/server/account.spec.js": "extended", }, "complete": function(data, {logger}) { logger.log("To get started:"); From 1eb749cb40c1b89cc57ba67f2a907ed4e04fb299 Mon Sep 17 00:00:00 2001 From: Walker Leite Date: Wed, 13 Dec 2017 15:09:03 -0200 Subject: [PATCH 24/25] fix(test): fix test commands --- test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test.js b/test.js index c688224..b366942 100644 --- a/test.js +++ b/test.js @@ -11,7 +11,7 @@ const initNoExtQuestions = [ const commands = [ {cmd: 'rm', args: ['-r', 'test-project'], ignoreErrors: true}, - {cmd: './node_modules/.bin/vue', args: ['init', '.', 'test-project'], responses: initNoExtQuestions}, + {cmd: './node_modules/.bin/vue', args: ['init', '.', 'test-project'], responses: initQuestions}, {cmd: 'npm', args: ['install'], cwd: 'test-project'}, {cmd: 'npm', args: ['run', 'lint'], cwd: 'test-project'}, {cmd: 'npm', args: ['run', 'test:server'], cwd: 'test-project'}, From e9cbf520d2767b2f8eea3b70773d10364f9a1448 Mon Sep 17 00:00:00 2001 From: Walker Leite Date: Wed, 13 Dec 2017 15:20:42 -0200 Subject: [PATCH 25/25] fix(template): escape curly braces and translate strings to en --- template/client/view/Login.vue | 4 ++-- template/client/view/Profile.vue | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/template/client/view/Login.vue b/template/client/view/Login.vue index 9f0da17..c13655c 100644 --- a/template/client/view/Login.vue +++ b/template/client/view/Login.vue @@ -28,7 +28,7 @@ + v-if="error">\{{error.message}}