diff --git a/Dockerfile b/Dockerfile index 94db6085e..7efe70614 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,7 +13,7 @@ RUN apt-get update \ && npm -g install pm2 WORKDIR /salix -COPY package.json . +COPY package.json package-lock.json ./ COPY loopback/package.json loopback/ RUN npm install --only=prod @@ -22,9 +22,9 @@ COPY back back COPY modules modules COPY dist/webpack-assets.json dist/ COPY \ - modules.yml \ - LICENSE \ - README.md \ + modules.yml \ + LICENSE \ + README.md \ ./ -CMD pm2-docker ./loopback/server/server.js +CMD ["pm2-docker", "./loopback/server/server.js"] diff --git a/Jenkinsfile b/Jenkinsfile index d8d587d9f..e6a00a7cb 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,11 +1,14 @@ #!/usr/bin/env groovy switch (env.BRANCH_NAME) { + case 'master': + env.NODE_ENV = 'production' + break; case 'test': env.NODE_ENV = 'test' break; - case 'master': - env.NODE_ENV = 'production' + case 'dev': + env.NODE_ENV = 'development' break; } @@ -18,8 +21,7 @@ node { } stage ('Install Node dependencies') { nodejs('node-lts') { - sh "npm install --only=prod" - sh "npm install --only=dev" + sh "NODE_ENV="" npm install --no-audit" sh "gulp install" } } @@ -32,6 +34,7 @@ node { withCredentials([dockerCert(credentialsId: 'docker', variable: 'DOCKER_CERT_PATH')]) { env.COMPOSE_PROJECT_NAME = 'salix' env.DOCKER_TLS_VERIFY = 1 + env.TAG = env.NODE_ENV if (env.BRANCH_NAME != 'master') { env.COMPOSE_PROJECT_NAME = "${env.BRANCH_NAME}-salix" diff --git a/back/methods/account/login.js b/back/methods/account/login.js new file mode 100644 index 000000000..59ce78690 --- /dev/null +++ b/back/methods/account/login.js @@ -0,0 +1,92 @@ +const url = require('url'); +const md5 = require('md5'); + +module.exports = Self => { + Self.remoteMethod('login', { + description: 'Login a user with username/email and password', + accepts: [ + { + arg: 'user', + type: 'String', + description: 'The user name or email', + required: true + }, { + arg: 'password', + type: 'String', + description: 'The user name or email', + required: true + }, { + arg: 'location', + type: 'String', + description: 'Location to redirect after login' + } + ], + returns: { + type: 'object', + root: true + }, + http: { + path: `/login`, + verb: 'POST' + } + }); + + Self.login = async function(user, password, location) { + let token; + let usesEmail = user.indexOf('@') !== -1; + let User = Self.app.models.User; + + let loginInfo = {password}; + + if (usesEmail) + loginInfo.email = user; + else + loginInfo.username = user; + + try { + token = await User.login(loginInfo, 'user'); + } catch (err) { + if (err.code != 'LOGIN_FAILED' || usesEmail) + throw err; + + let filter = {where: {name: user}}; + let instance = await Self.findOne(filter); + + if (!instance || instance.password !== md5(password)) + throw err; + + let where = {id: instance.id}; + let userData = { + id: instance.id, + username: user, + password: password, + email: instance.email, + created: instance.created, + updated: instance.updated + }; + await User.upsertWithWhere(where, userData); + token = await User.login(loginInfo, 'user'); + } + + let apiKey; + let continueUrl; + + try { + let query = url.parse(location, true).query; + apiKey = query.apiKey; + continueUrl = query.continue; + } catch (e) { + continueUrl = null; + } + + let applications = Self.app.get('applications'); + if (!apiKey) apiKey = 'default'; + let loginUrl = applications[apiKey] || '/login'; + + return { + token: token.id, + continue: continueUrl, + loginUrl: loginUrl + }; + }; +}; diff --git a/back/methods/account/logout.js b/back/methods/account/logout.js new file mode 100644 index 000000000..515855267 --- /dev/null +++ b/back/methods/account/logout.js @@ -0,0 +1,25 @@ +module.exports = Self => { + Self.remoteMethod('logout', { + description: 'Logout a user with access token', + accepts: [ + { + arg: 'ctx', + type: 'Object', + http: {source: 'context'} + } + ], + returns: { + type: 'Boolean', + root: true + }, + http: { + path: `/logout`, + verb: 'POST' + } + }); + + Self.logout = async function(ctx) { + await Self.app.models.User.logout(ctx.req.accessToken.id); + return true; + }; +}; diff --git a/back/models/account.js b/back/models/account.js index 6426bc3a7..dbf3cdabe 100644 --- a/back/models/account.js +++ b/back/models/account.js @@ -1,6 +1,9 @@ const md5 = require('md5'); module.exports = Self => { + require('../methods/account/login')(Self); + require('../methods/account/logout')(Self); + // Validations Self.validatesUniquenessOf('name', { diff --git a/back/models/account.json b/back/models/account.json index e087208d6..00d031f9e 100644 --- a/back/models/account.json +++ b/back/models/account.json @@ -31,5 +31,20 @@ "updated": { "type": "date" } - } + }, + "acls": [ + { + "property": "login", + "accessType": "EXECUTE", + "principalType": "ROLE", + "principalId": "$everyone", + "permission": "ALLOW" + }, { + "property": "logout", + "accessType": "EXECUTE", + "principalType": "ROLE", + "principalId": "$authenticated", + "permission": "ALLOW" + } + ] } diff --git a/services_tests.js b/back/tests.js similarity index 75% rename from services_tests.js rename to back/tests.js index d64b02447..ac3372d80 100644 --- a/services_tests.js +++ b/back/tests.js @@ -11,20 +11,20 @@ let verbose = false; if (process.argv[2] === '--v') verbose = true; -serviceRoot = `${__dirname}/loopback`; +serviceRoot = `${__dirname}/../loopback`; let Jasmine = require('jasmine'); let jasmine = new Jasmine(); let SpecReporter = require('jasmine-spec-reporter').SpecReporter; let serviceSpecs = [ - 'loopback/**/*[sS]pec.js', - 'back/**/*[sS]pec.js' + `${__dirname}/**/*[sS]pec.js`, + `${__dirname}/../loopback/**/*[sS]pec.js` ]; -let services = require(`./modules.yml`); +let services = require(`../modules.yml`); for (let service of services) - serviceSpecs.push(`modules/${service}/back/**/*[sS]pec.js`); + serviceSpecs.push(`${__dirname}/../modules/${service}/back/**/*[sS]pec.js`); jasmine.loadConfig({ spec_dir: '.', diff --git a/docker-compose.yml b/docker-compose.yml index 50c1a9451..9787368aa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,15 +6,15 @@ services: dockerfile: services/nginx/Dockerfile ports: - ${PORT}:80 - image: salix-app:${BRANCH_NAME} + image: salix-app:${TAG} restart: unless-stopped links: - api api: build: . environment: - NODE_ENV: ${NODE_ENV} + - NODE_ENV restart: unless-stopped - image: salix-api:${BRANCH_NAME} + image: salix-api:${TAG} volumes: - /containers/salix:/etc/salix diff --git a/smokes_tests.js b/e2e/smokes-tests.js similarity index 84% rename from smokes_tests.js rename to e2e/smokes-tests.js index a9c26446c..7b4e16edf 100644 --- a/smokes_tests.js +++ b/e2e/smokes-tests.js @@ -8,18 +8,17 @@ process.on('warning', warning => { let verbose = false; -if (process.argv[2] === '--v') +if (process.argv[2] === '--v') verbose = true; - let Jasmine = require('jasmine'); let jasmine = new Jasmine(); let SpecReporter = require('jasmine-spec-reporter').SpecReporter; jasmine.loadConfig({ spec_files: [ - './e2e/smokes/**/*[sS]pec.js', - './e2e/helpers/extensions.js' + `${__dirname}/smokes/**/*[sS]pec.js`, + `${__dirname}/helpers/extensions.js` ], helpers: [] }); diff --git a/e2e_tests.js b/e2e/tests.js similarity index 89% rename from e2e_tests.js rename to e2e/tests.js index 1b35bb8b2..08c1919ed 100644 --- a/e2e_tests.js +++ b/e2e/tests.js @@ -19,8 +19,8 @@ let SpecReporter = require('jasmine-spec-reporter').SpecReporter; jasmine.loadConfig({ spec_files: [ - './e2e/**/*[sS]pec.js', - './e2e/helpers/extensions.js' + `${__dirname}/**/*[sS]pec.js`, + `${__dirname}/helpers/extensions.js` ], helpers: [] }); diff --git a/front/test_index.js b/front/test-index.js similarity index 100% rename from front/test_index.js rename to front/test-index.js diff --git a/gulpfile.js b/gulpfile.js index 08db729ac..cb5dfc5a6 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -66,29 +66,21 @@ gulp.task('services-only', callback => { * Runs the e2e tests, restoring the fixtures first. */ gulp.task('e2e', ['docker'], async() => { - await runSequenceP('e2e-only'); -}); - -/** - * Runs the e2e tests. - */ -gulp.task('e2e-only', () => { const jasmine = require('gulp-jasmine'); if (argv.show || argv.s) process.env.E2E_SHOW = true; - return gulp.src('./e2e_tests.js') + return gulp.src('./e2e/tests.js') .pipe(jasmine({reporter: 'none'})); }); +/** + * Runs the smokes tests, restoring the fixtures first. + */ gulp.task('smokes', ['docker'], async() => { - await runSequenceP('smokes-only'); -}); - -gulp.task('smokes-only', () => { const jasmine = require('gulp-jasmine'); - return gulp.src('./smokes-tests.js') + return gulp.src('./e2e/smokes-tests.js') .pipe(jasmine({reporter: 'none'})); }); @@ -390,73 +382,42 @@ gulp.task('watch', function() { // Docker /** - * Rebuilds the docker, if already exists, destroys and - * rebuild it. + * Rebuilds the docker, if already exists, destroys and rebuild it. + * Also, if the container or it's image doesn't exists builds them. */ gulp.task('docker', async() => { try { - await execP('docker rm -fv dblocal'); + await execP('docker rm -fv salix-db'); } catch (e) {} - await runSequenceP('docker-run'); -}); - -/** - * Rebuilds the docker image, if already exists, destroys and - * rebuild it. calls upon docker task afterwards. - */ -gulp.task('docker-build', async() => { - try { - await execP('docker rm -fv dblocal'); - } catch (e) {} - try { - await execP('docker rmi dblocal:latest'); - } catch (e) {} - try { - await execP('docker volume rm data'); - } catch (e) {} - - log('Building image...'); - await execP('docker build -t dblocal:latest ./services/db'); - - await runSequenceP('docker'); + await execP('docker build -t salix-db ./services/db'); + await execP('docker run -d --name salix-db -p 3306:3306 salix-db'); }); /** * Does the minium effort to start the docker, if it doesn't exists calls - * the 'docker-run' task, if it is started does nothing. Keep in mind that when + * the 'docker' task, if it is started does nothing. Keep in mind that when * you do not rebuild the docker you may be using an outdated version of it. * See the 'docker' task for more info. */ gulp.task('docker-start', async() => { let state; try { - let result = await execP('docker container inspect -f "{{json .State}}" dblocal'); + let result = await execP('docker container inspect -f "{{json .State}}" salix-db'); state = JSON.parse(result.stdout); } catch (err) { - return await runSequenceP('docker-run'); + return await runSequenceP('docker'); } switch (state.Status) { case 'running': return; case 'exited': - return await execP('docker start dblocal'); - default: - throw new Error(`Unknown docker status: ${status}`); - } -}); - -/** - * Runs the docker, if the container or it's image doesn't exists builds them. - */ -gulp.task('docker-run', async() => { - try { - await execP('docker image inspect -f "{{json .Id}}" dblocal'); - await execP('docker run -d --name dblocal --volume data:/data -p 3306:3306 dblocal'); + await execP('docker start salix-db'); await runSequenceP('docker-wait'); - } catch (err) { - await runSequenceP('docker-build'); + return; + default: + throw new Error(`Unknown docker status: ${state.Status}`); } }); @@ -466,9 +427,9 @@ gulp.task('docker-run', async() => { gulp.task('docker-wait', callback => { const mysql = require('mysql2'); - let interval = 1; + let interval = 100; let elapsedTime = 0; - let maxInterval = 30 * 60; + let maxInterval = 30 * 60 * 1000; log('Waiting for MySQL init process...'); checker(); @@ -478,7 +439,7 @@ gulp.task('docker-wait', callback => { let state; try { - let result = await execP('docker container inspect -f "{{json .State}}" dblocal'); + let result = await execP('docker container inspect -f "{{json .State}}" salix-db'); state = JSON.parse(result.stdout); } catch (err) { return callback(new Error(err.message)); @@ -500,7 +461,7 @@ gulp.task('docker-wait', callback => { if (elapsedTime >= maxInterval) callback(new Error(`MySQL not initialized whithin ${elapsedTime} secs`)); else - setTimeout(checker, interval * 1000); + setTimeout(checker, interval); }); } }); diff --git a/karma.conf.js b/karma.conf.js index 948e5a7c1..addf1dc55 100644 --- a/karma.conf.js +++ b/karma.conf.js @@ -22,7 +22,7 @@ module.exports = function(config) { // list of files / patterns to load in the browser files: [ - {pattern: 'front/test_index.js', watched: false} + {pattern: 'front/test-index.js', watched: false} ], // list of files to exclude @@ -41,7 +41,7 @@ module.exports = function(config) { // preprocess matching files before serving them to the browser // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor preprocessors: { - './front/test_index.js': ['webpack', 'sourcemap'] + './front/test-index.js': ['webpack', 'sourcemap'] }, // test results reporter to use diff --git a/loopback/server/middleware/cors.js b/loopback/server/middleware/cors.js index 73c0d6832..835f96350 100644 --- a/loopback/server/middleware/cors.js +++ b/loopback/server/middleware/cors.js @@ -1,4 +1,4 @@ -var cors = require('cors'); +let cors = require('cors'); module.exports = function() { return cors({origin: true}); diff --git a/loopback/server/middleware/current-user.js b/loopback/server/middleware/current-user.js index 253d09f83..a6624351e 100644 --- a/loopback/server/middleware/current-user.js +++ b/loopback/server/middleware/current-user.js @@ -6,9 +6,9 @@ module.exports = function(options) { let LoopBackContext = require('loopback-context'); let loopbackContext = LoopBackContext.getCurrentContext(); - if (loopbackContext) { + if (loopbackContext) loopbackContext.set('currentUser', req.accessToken.userId); - } + next(); }; }; diff --git a/loopback/server/server.js b/loopback/server/server.js index 2128f465b..f37399f5d 100644 --- a/loopback/server/server.js +++ b/loopback/server/server.js @@ -62,23 +62,8 @@ if (fs.existsSync(viewDir)) { app.use(loopback.static(viewDir)); } -let assetsPath = [buildDir]; - -let wpAssets; -let wpAssetsFound = false; - -for (let assetsDir of assetsPath) { - let wpAssetsFile = `${assetsDir}/webpack-assets.json`; - wpAssetsFound = fs.existsSync(wpAssetsFile); - if (wpAssetsFound) { - wpAssets = require(wpAssetsFile); - break; - } -} - app.getWpAssets = entryPoint => { - if (!wpAssetsFound) - throw new Error('Webpack assets file not found: webpack-assets.json'); + const wpAssets = require(`${buildDir}/webpack-assets.json`); let jsFiles = []; let regex = new RegExp(`(^|~)${entryPoint}($|~)`); diff --git a/package.json b/package.json index 92aa2c14e..c6b700e92 100644 --- a/package.json +++ b/package.json @@ -87,9 +87,9 @@ "url": "https://git.verdnatura.es/salix" }, "scripts": { - "test": "nodemon -q services_tests.js -w services", - "dbtest": "nodemon -q db_tests.js -w services/db/tests", - "lint": "eslint ./ --cache --ignore-pattern .gitignore", - "services": "nodemon --inspect -w services ./node_modules/gulp/bin/gulp.js services" + "test": "nodemon -q back/tests.js -w modules", + "dbtest": "nodemon -q services/db/tests.js -w services/db/tests", + "services": "nodemon --inspect -w modules ./node_modules/gulp/bin/gulp.js services", + "lint": "eslint ./ --cache --ignore-pattern .gitignore" } } diff --git a/services/db/Dockerfile b/services/db/Dockerfile index 6acbcdac4..3f443c5c3 100644 --- a/services/db/Dockerfile +++ b/services/db/Dockerfile @@ -1,12 +1,30 @@ -FROM verdnatura/vn-mysql:latest +FROM mysql:5.6 + ENV MYSQL_ROOT_PASSWORD root -ENV TZ=Europe/Madrid +ENV TZ Europe/Madrid + +RUN cp /usr/local/bin/docker-entrypoint.sh /usr/local/bin/docker-init.sh \ + && sed -i '$ d' /usr/local/bin/docker-init.sh + +ARG DEBIAN_FRONTEND=noninteractive +RUN apt-get update \ + && apt-get install -y --no-install-recommends curl ca-certificates \ + && curl -L https://apt.verdnatura.es/conf/verdnatura.gpg | apt-key add - \ + && echo "deb http://apt.verdnatura.es/ jessie main" > /etc/apt/sources.list.d/vn.list \ + && apt-get update \ + && apt-get install -y vn-mysql \ + && apt-get purge -y --auto-remove curl ca-certificates \ + && rm -rf /var/lib/apt/lists/* + WORKDIR /docker-entrypoint-initdb.d -COPY install ./ -RUN chmod -R 777 . -RUN mkdir /data -RUN chmod 777 /data -CMD ["mysqld"] +COPY install /docker-entrypoint-initdb.d +RUN mkdir /mysql-data \ + && /usr/local/bin/docker-init.sh mysqld --datadir /mysql-data \ + && chown -R mysql:mysql /mysql-data \ + && rm -rf /docker-entrypoint-initdb.d/* + +USER mysql +CMD ["mysqld", "--datadir", "/mysql-data"] + #HEALTHCHECK --interval=5s --timeout=10s --retries=200 \ # CMD mysqladmin ping -h 127.0.0.1 -u root || exit 1 -EXPOSE 3306 \ No newline at end of file diff --git a/services/db/install/boot.sh b/services/db/install/boot.sh old mode 100644 new mode 100755 index e63215174..70704c48e --- a/services/db/install/boot.sh +++ b/services/db/install/boot.sh @@ -1,32 +1,24 @@ #!/bin/bash export MYSQL_PWD=root -if [ -d /data/mysql ]; then - cp -R /data/mysql /var/lib - echo "[INFO] -> Restored database to default state" -else - # Dump structure - echo "[INFO] -> Imported ./dump/truncateAll.sql" - mysql -u root -f < ./dump/truncateAll.sql - echo "[INFO] -> Imported ./dump/structure.sql" - mysql -u root -f < ./dump/structure.sql - echo "[INFO] -> Imported ./dump/mysqlPlugins.sql" - mysql -u root -f < ./dump/mysqlPlugins.sql +# Dump structure +echo "[INFO] -> Imported ./dump/truncateAll.sql" + mysql -u root -f < ./dump/truncateAll.sql +echo "[INFO] -> Imported ./dump/structure.sql" + mysql -u root -f < ./dump/structure.sql +echo "[INFO] -> Imported ./dump/mysqlPlugins.sql" + mysql -u root -f < ./dump/mysqlPlugins.sql - # Import changes - for file in changes/*/*.sql; do - echo "[INFO] -> Imported ./$file" - mysql -u root -fc < $file - done +# Import changes +for file in changes/*/*.sql; do + echo "[INFO] -> Imported ./$file" + mysql -u root -fc < $file +done - # Import fixtures - echo "[INFO] -> Imported ./dump/dumpedFixtures.sql" - mysql -u root -f < ./dump/dumpedFixtures.sql - echo "[INFO] -> Imported ./dump/fixtures.sql" - mysql -u root -f < ./dump/fixtures.sql +# Import fixtures +echo "[INFO] -> Imported ./dump/dumpedFixtures.sql" +mysql -u root -f < ./dump/dumpedFixtures.sql +echo "[INFO] -> Imported ./dump/fixtures.sql" +mysql -u root -f < ./dump/fixtures.sql - # Copy dumpted data to volume - cp -R /var/lib/mysql /data - - echo "[INFO] -> Dumped database" -fi \ No newline at end of file +echo "[INFO] -> Dumped database" diff --git a/db_tests.js b/services/db/tests.js similarity index 89% rename from db_tests.js rename to services/db/tests.js index a20d26bb6..88bac1488 100644 --- a/db_tests.js +++ b/services/db/tests.js @@ -9,14 +9,14 @@ let verbose = false; if (process.argv[2] === '--v') verbose = true; -loopbackApp = `${__dirname}/loopback/server/server`; +serviceRoot = __dirname; let Jasmine = require('jasmine'); let jasmine = new Jasmine(); let SpecReporter = require('jasmine-spec-reporter').SpecReporter; let serviceSpecs = [ - 'db/tests/**/*[sS]pec.js' + './db/tests/**/*[sS]pec.js' ]; jasmine.loadConfig({ diff --git a/services/nginx/Dockerfile b/services/nginx/Dockerfile index dce275b08..08f65d9fe 100644 --- a/services/nginx/Dockerfile +++ b/services/nginx/Dockerfile @@ -7,13 +7,13 @@ RUN apt-get update \ && apt-get install -y apt-utils \ && apt-get install -y --no-install-recommends nginx +RUN ln -sf /dev/stdout /var/log/nginx/access.log \ + && ln -sf /dev/stderr /var/log/nginx/error.log + WORKDIR /etc/nginx COPY services/nginx/temp/nginx.conf sites-available/salix RUN rm sites-enabled/default && ln -s ../sites-available/salix sites-enabled/salix -RUN ln -sf /dev/stdout /var/log/nginx/access.log \ - && ln -sf /dev/stderr /var/log/nginx/error.log - COPY dist /salix/dist CMD ["nginx", "-g", "daemon off;"]