From 47de0a4785f98188ab1f4f4edbb436674c8a6d37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Mon, 13 Aug 2018 20:12:39 +0200 Subject: [PATCH 01/14] Complete rewrite of configuration - no bash scripts * error prone * platform dependent) --- .env.example | 18 ++++++++++++++++++ .env.template | 18 ++++++++++++++++++ .env.tmp | 35 ----------------------------------- .gitignore | 1 - .travis.yml | 3 +-- README.md | 8 +++++++- entrypoint.sh | 12 ------------ middleware/authenticated.js | 4 ++-- middleware/maintenance.js | 4 ++-- nuxt.config.js | 17 ++++++++++++++--- package.json | 22 ++++++++++------------ plugins/api.js | 10 +++++----- plugins/env.js | 3 +-- plugins/i18n.js | 4 ++-- plugins/raven-client.js | 12 ++++++------ plugins/raven-server.js | 15 +++++---------- scripts/on-build.sh | 14 -------------- scripts/on-dev.sh | 4 ---- scripts/on-start.sh | 24 ------------------------ scripts/run-local.sh | 5 ----- server/.env-secrets.tmp | 2 -- server/index.js | 24 +++++++++--------------- store/env.js | 25 ------------------------- yarn.lock | 2 +- 24 files changed, 101 insertions(+), 185 deletions(-) create mode 100644 .env.example create mode 100644 .env.template delete mode 100644 .env.tmp delete mode 100644 entrypoint.sh delete mode 100644 scripts/on-build.sh delete mode 100644 scripts/on-dev.sh delete mode 100644 scripts/on-start.sh delete mode 100644 scripts/run-local.sh delete mode 100644 server/.env-secrets.tmp delete mode 100644 store/env.js diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..0a3085cb --- /dev/null +++ b/.env.example @@ -0,0 +1,18 @@ +# WEBAPP +WEBAPP_HOST = localhost +WEBAPP_PORT = 3000 +WEBAPP_BASE_URL = http://localhost:3000 + +# API +API_HOST = localhost +API_PORT = 3030 + +# METAAPI +EMBED_API_URL = http://localhost:3050 + +# 3rd party integrations +SENTRY_DNS_PUBLIC = +MAPBOX_TOKEN = pk.eyJ1IjoiaHVtYW4tY29ubmVjdGlvbiIsImEiOiJjajl0cnBubGoweTVlM3VwZ2lzNTNud3ZtIn0.KZ8KK9l70omjXbEkkbHGsQ + +# MAINTENANCE +MAINTENANCE = diff --git a/.env.template b/.env.template new file mode 100644 index 00000000..25d99134 --- /dev/null +++ b/.env.template @@ -0,0 +1,18 @@ +# WEBAPP +WEBAPP_HOST = ${WEBAPP_HOST} +WEBAPP_PORT = ${WEBAPP_PORT} +WEBAPP_BASE_URL = ${WEBAPP_BASE_URL} + +# API +API_HOST = ${API_HOST} +API_PORT = ${API_PORT} + +# METAAPI +EMBED_API_URL = ${EMBED_API_URL} + +# 3rd party integrations +SENTRY_DNS_PUBLIC = ${SENTRY_DNS_PUBLIC} +MAPBOX_TOKEN = ${MAPBOX_TOKEN} + +# MAINTENANCE +MAINTENANCE = ${MAINTENANCE} diff --git a/.env.tmp b/.env.tmp deleted file mode 100644 index 391cc5e7..00000000 --- a/.env.tmp +++ /dev/null @@ -1,35 +0,0 @@ -# DO NOT INPUT SECRETS OR PRIVAT TOKENS! -# for secrets please use server/.env-secrets -# -# this file here is read on server and client - -#################################### -# at build time -#################################### -BUILD_DATE = ${BUILD_DATE} -BUILD_ID = ${TRAVIS_BUILD_NUMBER} -BUILD_COMMIT = ${TRAVIS_PULL_REQUEST_SHA} - -#################################### -# at deploy time -#################################### -DEPLOY_DATE = ${DEPLOY_DATE} - -# WEBAPP -WEBAPP_HOST = ${WEBAPP_HOST} -WEBAPP_PORT = ${WEBAPP_PORT} -WEBAPP_BASE_URL = ${WEBAPP_BASE_URL} - -# API -API_HOST = ${API_HOST} -API_PORT = ${API_PORT} - -# METAAPI -EMBED_API_URL = ${EMBED_API_URL} - -# 3rd party integrations -SENTRY_DNS_PUBLIC = ${SENTRY_DNS_PUBLIC} -MAPBOX_TOKEN = ${MAPBOX_TOKEN} - -# MAINTENANCE -MAINTENANCE = ${MAINTENANCE} diff --git a/.gitignore b/.gitignore index 43ab0ac6..86bb3684 100644 --- a/.gitignore +++ b/.gitignore @@ -26,7 +26,6 @@ npm-debug.log /nbproject/private/ /nbproject/ -# .env and .env-secrets .env .env-secrets diff --git a/.travis.yml b/.travis.yml index 5ac3e772..9d6a66bc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,8 +16,7 @@ jobs: - stage: Test and Build script: - yarn install --frozen-lockfile --non-interactive - - yarn test:env - - yarn test + - yarn run ci - script: - docker build -t humanconnection/frontend-nuxt . diff --git a/README.md b/README.md index 32217340..af05c333 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ This is the nuxt + express version of our WebApp as nuxt.js seams to be more stable and we have better options for keeping it updated. -## Build Setup +## Local installation > we recommend to install the project locally for the best development ease and performance @@ -34,6 +34,12 @@ $ yarn dev $ yarn start ``` +Create your individual set of environment variables: +```sh +$ cp .env.example .env +# now open .env and change it according to your setup +``` + For detailed explanation on how things work, checkout the [Nuxt.js docs](https://github.com/nuxt/nuxt.js). ## Env Vars diff --git a/entrypoint.sh b/entrypoint.sh deleted file mode 100644 index d073171f..00000000 --- a/entrypoint.sh +++ /dev/null @@ -1,12 +0,0 @@ -#!/usr/bin/env ash - -# this script is triggered always when the image is started -# it writes the environment variables to the config files -# this is needed as nuxtjs replaces all mentions of process.env -# with static vars at build time but we need them fresh at deploy time - -# setting deploy time environment vars and writing them to .env -./scripts/on-start.sh - -# starting the application with the correct settings -yarn start -p $WEBAPP_PORT -H $WEBAPP_HOST diff --git a/middleware/authenticated.js b/middleware/authenticated.js index b74c0363..a0e723eb 100644 --- a/middleware/authenticated.js +++ b/middleware/authenticated.js @@ -1,7 +1,7 @@ import { isEmpty } from 'lodash' -export default async ({ store, route, redirect }) => { - let publicPages = process.env.publicPages +export default async ({ store, env, route, redirect }) => { + let publicPages = env.publicPages publicPages.push('auth-logout') // only affect non public pages if (publicPages.indexOf(route.name) >= 0) { diff --git a/middleware/maintenance.js b/middleware/maintenance.js index c1b4c073..da242d2f 100644 --- a/middleware/maintenance.js +++ b/middleware/maintenance.js @@ -1,7 +1,7 @@ -export default async function ({ app, error, store, redirect, route }) { +export default async function ({ app, env, error, store, redirect, route }) { let isMaintenanceEnabled = false - if (Boolean(app.$env.MAINTENANCE) === true) { + if (Boolean(env.MAINTENANCE) === true) { error({ statusCode: 503, message: 'Maintenance Mode' }) return } diff --git a/nuxt.config.js b/nuxt.config.js index 6e88623d..e01ca888 100644 --- a/nuxt.config.js +++ b/nuxt.config.js @@ -1,4 +1,3 @@ -require('dotenv').config() const path = require('path') module.exports = { @@ -112,8 +111,20 @@ module.exports = { {src: '~/plugins/open-page-in-modal.js', ssr: false} ], modules: [ - 'cookie-universal-nuxt', - '@nuxtjs/dotenv' + ['@nuxtjs/dotenv', { + only: [ + 'WEBAPP_HOST', + 'WEBAPP_PORT', + 'WEBAPP_BASE_URL', + 'API_HOST', + 'API_PORT', + 'EMBED_API_URL', + 'SENTRY_DNS_PUBLIC', + 'MAPBOX_TOKEN', + 'MAINTENANCE' + ] + }], + 'cookie-universal-nuxt' // '@nuxtjs/pwa' ], router: { diff --git a/package.json b/package.json index 617c428d..3e6ac1b6 100644 --- a/package.json +++ b/package.json @@ -27,22 +27,20 @@ "yarn": ">= 1.3.0" }, "scripts": { - "dev": "yarn dev:env && backpack dev", - "dev:local": "sh scripts/run-local.sh", - "dev:debug": "yarn dev:env && backpack dev --inspect-brk=9229", - "dev:env": "sh scripts/on-dev.sh", - "build": "yarn build:env && nuxt build && backpack build", - "build:env": "sh scripts/on-build.sh", - "start": "yarn start:env && node build/main.js", - "start:pm2": "yarn start:env && pm2 start node build/main.js -n frontend -i 2 --attach", - "start:env": "sh scripts/on-start.sh", - "refresh": "rm -rf node_modules && yarn install && npm run dev", + "ci": "yarn run eslint && yarn run test", + "clean": "rm -rf build/*", + "dev": "backpack dev", + "dev:debug": "backpack dev --inspect=0.0.0.0:9229", + "envsub": "envsub .env.template .env", + "build": "nuxt build && backpack build", + "start": "node build/main.js", + "start:pm2": "pm2 start node build/main.js -n frontend -i 2 --attach", + "deploy": "yarn run envsub && yarn run build && yarn run start:pm2", "precommit": "yarn eslint", "eslint": "eslint --ext .js,.vue .", "styleguide": "vue-styleguidist server", "styleguide:build": "vue-styleguidist build", "test": "ava", - "test:env": "sh scripts/on-dev.sh", "cypress:open": "cypress open", "cypress:run": "cypress run", "coverage": "nyc report --reporter=text-lcov > coverage.lcov && codecov" @@ -52,7 +50,7 @@ "@feathersjs/client": "^3.5.3", "@feathersjs/feathers": "^3.1.7", "@feathersjs/socketio": "^3.2.2", - "@nuxtjs/dotenv": "~1.1.0", + "@nuxtjs/dotenv": "^1.1.1", "@nuxtjs/pwa": "~2.0.5", "axios": "^0.18.0", "babel-eslint": "~8.2.5", diff --git a/plugins/api.js b/plugins/api.js index 29633145..9794ac34 100644 --- a/plugins/api.js +++ b/plugins/api.js @@ -5,9 +5,9 @@ import authentication from '@feathersjs/authentication-client' import urlHelper from '~/helpers/urls' import Vue from 'vue' -export default ({app, store, redirect, router}) => { +export default ({app, env, store, redirect, router}) => { const authKey = 'feathers-jwt' - const endpoint = urlHelper.buildEndpointURL(app.$env.API_HOST, { port: app.$env.API_PORT }) + const endpoint = urlHelper.buildEndpointURL(env.API_HOST, { port: env.API_PORT }) const storage = { getItem: (key) => { const res = app.$cookies.get(key) @@ -26,7 +26,7 @@ export default ({app, store, redirect, router}) => { }, clear: () => { const res = app.$cookies.removeAll() - if (process.env.NODE_ENV === 'development') { + if (env.NODE_ENV === 'development') { console.log(`## STORAGE: clear()`, res) } return res @@ -60,7 +60,7 @@ export default ({app, store, redirect, router}) => { all: [ async (hook) => { // hook.accessToken = await api.passport.getJWT() - if (process.env.NODE_ENV === 'development') { + if (env.NODE_ENV === 'development') { console.log('# API:', `${hook.method} ${hook.path}`) console.info('data', hook.data) // console.log('# ' + hook.accessToken) @@ -70,7 +70,7 @@ export default ({app, store, redirect, router}) => { ] }, async error (ctx) { - if (process.env.NODE_ENV === 'development') { + if (env.NODE_ENV === 'development') { console.log('####################') console.error(ctx.error) console.info('JWT TOKEN: ', app.$cookies.get(authKey)) diff --git a/plugins/env.js b/plugins/env.js index 62005f95..d2081cc2 100644 --- a/plugins/env.js +++ b/plugins/env.js @@ -1,8 +1,7 @@ import Vue from 'vue' export default async (context) => { - await context.store.dispatch('env/init', context) - context.app.$env = context.store.getters['env/all'] + context.app.$env = context.env Vue.use({ install (Vue, store) { diff --git a/plugins/i18n.js b/plugins/i18n.js index dc006ab2..7b4dd790 100644 --- a/plugins/i18n.js +++ b/plugins/i18n.js @@ -4,8 +4,8 @@ import { debounce, isEmpty } from 'lodash' import vuexI18n from 'vuex-i18n/dist/vuex-i18n.umd.js' -export default ({ app, req, cookie, store }) => { - const doDebug = process.env.NODE_ENV !== 'production' +export default ({ app, env, req, cookie, store }) => { + const doDebug = env.NODE_ENV !== 'production' const key = 'locale' const changeHandler = debounce((mutation, store) => { diff --git a/plugins/raven-client.js b/plugins/raven-client.js index 7508d33c..0b3cc1dd 100644 --- a/plugins/raven-client.js +++ b/plugins/raven-client.js @@ -2,14 +2,14 @@ import Vue from 'vue' import Raven from 'raven-js' import RavenVue from 'raven-js/plugins/vue' -export default ({app}) => { - if (process.browser && app.$env.SENTRY_DNS_PUBLIC) { +export default ({env}) => { + if (process.browser && env.SENTRY_DNS_PUBLIC) { Raven - .config(app.$env.SENTRY_DNS_PUBLIC, { - release: app.$env.BUILD_DATE, - environment: app.$env.NODE_ENV, + .config(env.SENTRY_DNS_PUBLIC, { + release: env.BUILD_DATE, + environment: env.NODE_ENV, tags: { - deployed: app.$env.DEPLOY_DATE, + deployed: env.DEPLOY_DATE, client: true } }) diff --git a/plugins/raven-server.js b/plugins/raven-server.js index 309cf0b4..6174a99e 100644 --- a/plugins/raven-server.js +++ b/plugins/raven-server.js @@ -1,19 +1,14 @@ import Raven from 'raven' -import dotenv from 'dotenv' -import {readFileSync} from 'fs' -import {resolve} from 'path' export default async function (app) { - const secrets = dotenv.parse(await readFileSync(resolve('./server/.env-secrets'))) - - if (!process.client && secrets.SENTRY_DNS_PRIVATE) { + if (!process.client && process.env.SENTRY_DNS_PRIVATE) { // LOGGING IS ENABLED console.log('SENTRY LOGGING IS ENABLED') - Raven.config(secrets.SENTRY_DNS_PRIVATE, { - release: app.$env.BUILD_COMMIT, - environment: app.$env.NODE_ENV, + Raven.config(process.env.SENTRY_DNS_PRIVATE, { + release: process.env.BUILD_COMMIT, + environment: process.env.NODE_ENV, tags: { - deployed: app.$env.DEPLOY_DATE, + deployed: process.env.DEPLOY_DATE, client: true } }).install() diff --git a/scripts/on-build.sh b/scripts/on-build.sh deleted file mode 100644 index c06c2641..00000000 --- a/scripts/on-build.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash - -# this script is triggered on build -# it writes the environment variables to the config files -# this is needed as nuxtjs replaces all mentions of process.env -# with static vars at build time but we need them fresh at deploy time - -# set the build datetime -export BUILD_DATE="$(date +'%Y-%m-%d %T')" - -# create .env from .env.tmp and fill in build vars -# while swill keep deploy time variables intact -envsub --protect .env.tmp .env -envsub --protect server/.env-secrets.tmp server/.env-secrets diff --git a/scripts/on-dev.sh b/scripts/on-dev.sh deleted file mode 100644 index 45013694..00000000 --- a/scripts/on-dev.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env bash - -sh ./scripts/on-build.sh -sh ./scripts/on-start.sh diff --git a/scripts/on-start.sh b/scripts/on-start.sh deleted file mode 100644 index f00bb2b8..00000000 --- a/scripts/on-start.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env ash - -# this script is triggered always when the image is started -# it writes the environment variables to the config files -# this is needed as nuxtjs replaces all mentions of process.env -# with static vars at build time but we need them fresh at deploy time - -# set the deploy datetime -export DEPLOY_DATE="$(date +'%Y-%m-%d %T')" - -# replace the remaining deploy time variables inside .env -# while also setting all non existing vars to empty values -envsub .env .env \ - --system \ - -e WEBAPP_BASE_URL=http://localhost:3000 \ - -e WEBAPP_HOST=localhost \ - -e WEBAPP_PORT=3000 \ - -e API_HOST=localhost \ - -e API_PORT=3030 \ - -e EMBED_API_URL=http://localhost:3050 \ - -e MAPBOX_TOKEN=pk.eyJ1IjoiaHVtYW4tY29ubmVjdGlvbiIsImEiOiJjajl0cnBubGoweTVlM3VwZ2lzNTNud3ZtIn0.KZ8KK9l70omjXbEkkbHGsQ -# empty remaining -envsub .env .env -envsub server/.env-secrets.tmp server/.env-secrets diff --git a/scripts/run-local.sh b/scripts/run-local.sh deleted file mode 100644 index e47a9a1b..00000000 --- a/scripts/run-local.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env bash - -# start app on the local network -LOCAL_IP=$(ifconfig | grep -Eo 'inet (addr:)?([0-9]*\.){3}[0-9]*' | grep -Eo '([0-9]*\.){3}[0-9]*' | grep -v '127.0.0.1'); -env WEBAPP_HOST=$LOCAL_IP env WEBAPP_BASE_URL=http://$LOCAL_IP:3000 env API_HOST=$LOCAL_IP yarn dev diff --git a/server/.env-secrets.tmp b/server/.env-secrets.tmp deleted file mode 100644 index 91da751d..00000000 --- a/server/.env-secrets.tmp +++ /dev/null @@ -1,2 +0,0 @@ -SENTRY_DNS_PRIVATE = ${SENTRY_DNS_PRIVATE} -EMBED_API_TOKEN = ${EMBED_API_TOKEN} \ No newline at end of file diff --git a/server/index.js b/server/index.js index dc00508e..bc854d93 100644 --- a/server/index.js +++ b/server/index.js @@ -9,10 +9,6 @@ import redirectSSL from 'redirect-ssl' import avatar from './avatar' import embeds from './embeds' import raven from '../plugins/raven-server' -import { readFileSync } from 'fs' - -// Get .env config -require('dotenv').config() const app = express() @@ -43,9 +39,7 @@ app.use('/embeds', embeds()) // Init Nuxt.js const nuxt = new Nuxt(nuxtConfig) -const env = require('dotenv').parse(readFileSync('./.env')) - -app.set('port', env.WEBAPP_PORT) +app.set('port', process.env.WEBAPP_PORT) // Build only in dev mode if (nuxtConfig.dev) { @@ -56,11 +50,11 @@ if (nuxtConfig.dev) { // Give nuxt middleware to express app.use(nuxt.render) // Listen the server -app.listen(env.WEBAPP_PORT, env.WEBAPP_HOST) -console.log(`Server listening on ${env.WEBAPP_HOST}:${env.WEBAPP_PORT}`) // eslint-disable-line no-console -console.log(`MAINTENANCE ${(Boolean(env.MAINTENANCE) === true).toString()}`) // eslint-disable-line no-console -console.log(`WEBAPP_PORT ${env.WEBAPP_PORT}`) // eslint-disable-line no-console -console.log(`WEBAPP_HOST ${env.WEBAPP_HOST}`) // eslint-disable-line no-console -console.log(`WEBAPP_BASE_URL ${env.WEBAPP_BASE_URL}`) // eslint-disable-line no-console -console.log(`API_PORT ${env.API_PORT}`) // eslint-disable-line no-console -console.log(`API_HOST ${env.API_HOST}`) // eslint-disable-line no-console +app.listen(process.env.WEBAPP_PORT, process.env.WEBAPP_HOST) +console.log(`Server listening on ${process.env.WEBAPP_HOST}:${process.env.WEBAPP_PORT}`) // eslint-disable-line no-console +console.log(`MAINTENANCE ${(Boolean(process.env.MAINTENANCE) === true).toString()}`) // eslint-disable-line no-console +console.log(`WEBAPP_PORT ${process.env.WEBAPP_PORT}`) // eslint-disable-line no-console +console.log(`WEBAPP_HOST ${process.env.WEBAPP_HOST}`) // eslint-disable-line no-console +console.log(`WEBAPP_BASE_URL ${process.env.WEBAPP_BASE_URL}`) // eslint-disable-line no-console +console.log(`API_PORT ${process.env.API_PORT}`) // eslint-disable-line no-console +console.log(`API_HOST ${process.env.API_HOST}`) // eslint-disable-line no-console diff --git a/store/env.js b/store/env.js deleted file mode 100644 index a1f59a5f..00000000 --- a/store/env.js +++ /dev/null @@ -1,25 +0,0 @@ -export const state = () => { - return { - env: {} - } -} - -export const mutations = { - SET_ENV (state, env) { - state.env = env - } -} - -export const getters = { - all (state) { - return state.env - } -} - -export const actions = { - init ({commit}) { - if (process.server) { - commit('SET_ENV', require('dotenv').config().parsed) - } - } -} diff --git a/yarn.lock b/yarn.lock index 420a8a0d..7aeb2146 100644 --- a/yarn.lock +++ b/yarn.lock @@ -250,7 +250,7 @@ version "3.0.0" resolved "https://registry.yarnpkg.com/@mapbox/whoots-js/-/whoots-js-3.0.0.tgz#c1de4293081424da3ac30c23afa850af1019bb54" -"@nuxtjs/dotenv@~1.1.0": +"@nuxtjs/dotenv@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@nuxtjs/dotenv/-/dotenv-1.1.1.tgz#c3d68f96d8f76cac4c4ddbc8268702eb95737bbe" dependencies: From a8408e9e45d57a4be232676e028c0f24dd74cedb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Mon, 13 Aug 2018 14:30:54 +0200 Subject: [PATCH 02/14] Simplify Dockerfile and docker-compose.yml --- Dockerfile | 43 +++++++------------------------------------ docker-compose.yml | 19 +++++++------------ 2 files changed, 14 insertions(+), 48 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9b0e039d..fec7e634 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,46 +1,17 @@ FROM node:8.9-alpine LABEL Description="This image is used to start the hc-frontend-nuxt" Vendor="Human-Connection gGmbH" Version="1.0" Maintainer="Human-Connection gGmbH (developer@human-connection.org)" -# update unix packages -RUN apk update && apk upgrade -RUN apk add git -RUN rm -rf /var/cache/apk/* - -# install global dependencies -RUN yarn global add pm2 envsub - # expose the app port EXPOSE 3000 -# set environment variables -ENV HOST=0.0.0.0 -ENV WEBAPP_HOST=0.0.0.0 - -ENTRYPOINT ["./entrypoint.sh"] +RUN apk update && apk upgrade && apk add git && rm -rf /var/cache/apk/* +RUN yarn global add pm2 envsub -# create working directory -RUN mkdir -p /var/www/ -WORKDIR /var/www/ +RUN mkdir -p /WebApp/ +WORKDIR /WebApp/ -# install app dependencies -COPY package.json /var/www/ -COPY yarn.lock /var/www/ +COPY package.json /WebApp/ +COPY yarn.lock /WebApp/ RUN yarn install --frozen-lockfile --non-interactive -# copy the code to the docker image -COPY . /var/www/ - -# set execution rights on scripts and run the build script -RUN chmod +x entrypoint.sh -RUN chmod +x scripts/on-build.sh -RUN chmod +x scripts/on-start.sh -RUN sh scripts/on-build.sh - -# buld application -# ENV NODE_ENV=production #we seam to have issues with the production flag on install && build -RUN yarn build - -ENV NODE_ENV=production - -# only keep production dependencies -# RUN yarn install --frozen-lockfile --non-interactive +COPY . /WebApp/ diff --git a/docker-compose.yml b/docker-compose.yml index 02600987..cd4c9b1c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,16 +1,11 @@ -version: '2' +version: '3' services: - frontend: + webapp: build: . - environment: - WEBAPP_HOST: localhost - WEBAPP_PORT: 3000 - WEBAPP_BASE_URL: http://localhost:3000 - API_HOST: http://localhost - API_PORT: 3030 - MAPBOX_TOKEN: - stdin_open: true - tty: true ports: - - 3000:3000/tcp + - 3000:3000/tcp + command: yarn run dev + volumes: + - .:/WebApp + - /WebApp/node_modules/ From 49ac83ed343d9f040300862120c37778ff7febba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Tue, 14 Aug 2018 17:43:52 +0200 Subject: [PATCH 03/14] Provide the BUILD_COMMIT on the build server --- .travis.yml | 2 +- Dockerfile | 6 +++++- docker-compose.yml | 5 ++++- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 9d6a66bc..ff558afe 100644 --- a/.travis.yml +++ b/.travis.yml @@ -18,7 +18,7 @@ jobs: - yarn install --frozen-lockfile --non-interactive - yarn run ci - script: - - docker build -t humanconnection/frontend-nuxt . + - docker build --build-arg BUILD_COMMIT=$TRAVIS_COMMIT -t humanconnection/frontend-nuxt . after_success: - if [ $TRAVIS_BRANCH == "master" ] && [ $TRAVIS_EVENT_TYPE == "push" ]; then diff --git a/Dockerfile b/Dockerfile index fec7e634..85d39d92 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,8 +4,12 @@ LABEL Description="This image is used to start the hc-frontend-nuxt" Vendor="Hum # expose the app port EXPOSE 3000 +# optional git commit hash +ARG BUILD_COMMIT +ENV BUILD_COMMIT=$BUILD_COMMIT + RUN apk update && apk upgrade && apk add git && rm -rf /var/cache/apk/* -RUN yarn global add pm2 envsub +RUN yarn global add pm2 RUN mkdir -p /WebApp/ WORKDIR /WebApp/ diff --git a/docker-compose.yml b/docker-compose.yml index cd4c9b1c..3551e18c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,10 @@ version: '3' services: webapp: - build: . + build: + context: . + args: + BUILD_COMMIT: ${BUILD_COMMIT} ports: - 3000:3000/tcp command: yarn run dev From b22a585b5562d781a97ed79ae2ffa24d91c9c150 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Wed, 15 Aug 2018 00:39:34 +0200 Subject: [PATCH 04/14] Update base image of Dockerfile --- Dockerfile | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 85d39d92..50a5ac51 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:8.9-alpine +FROM node:alpine LABEL Description="This image is used to start the hc-frontend-nuxt" Vendor="Human-Connection gGmbH" Version="1.0" Maintainer="Human-Connection gGmbH (developer@human-connection.org)" # expose the app port @@ -8,14 +8,22 @@ EXPOSE 3000 ARG BUILD_COMMIT ENV BUILD_COMMIT=$BUILD_COMMIT -RUN apk update && apk upgrade && apk add git && rm -rf /var/cache/apk/* -RUN yarn global add pm2 - RUN mkdir -p /WebApp/ WORKDIR /WebApp/ +# --no-cache: download package index on-the-fly, no need to cleanup afterwards +# --virtual: bundle packages, remove whole bundle at once, when done +RUN apk --no-cache --virtual build-dependencies add git python make g++ + +RUN yarn global add pm2 COPY package.json /WebApp/ COPY yarn.lock /WebApp/ RUN yarn install --frozen-lockfile --non-interactive +RUN apk del build-dependencies + +# must be after `yarn install` +ENV NODE_ENV=production + COPY . /WebApp/ +CMD ["pm2", "start", "node", "build/main.js", "-n", "frontend", "-i", "2", "--attach"] From 1c9efde18e229ede74d71b40fde39bd40bd35c7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Wed, 15 Aug 2018 00:40:39 +0200 Subject: [PATCH 05/14] Give image a name as on build server --- docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yml b/docker-compose.yml index 3551e18c..6a7a6ef6 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,6 +2,7 @@ version: '3' services: webapp: + image: humanconnection/frontend-nuxt build: context: . args: From 8f790f3b99b0809d3c5bfc0cfab6db35cca34f16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Wed, 15 Aug 2018 00:45:25 +0200 Subject: [PATCH 06/14] Provide a staging docker-compose.yml for WebApp --- .dockerignore | 3 +- Dockerfile | 8 ++--- docker-compose.override.yml | 8 +++++ docker-compose.staging.yml | 71 +++++++++++++++++++++++++++++++++++++ docker-compose.yml | 6 ++-- 5 files changed, 86 insertions(+), 10 deletions(-) create mode 100644 docker-compose.override.yml create mode 100644 docker-compose.staging.yml diff --git a/.dockerignore b/.dockerignore index 3285e1ae..bc2de8a4 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,5 +5,6 @@ node_modules/ build/ npm-debug.log Dockerfile -.env +docker-compose*.yml .env-secrets +.env diff --git a/Dockerfile b/Dockerfile index 50a5ac51..c8c3c765 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,6 +7,7 @@ EXPOSE 3000 # optional git commit hash ARG BUILD_COMMIT ENV BUILD_COMMIT=$BUILD_COMMIT +ENV NODE_ENV=production RUN mkdir -p /WebApp/ WORKDIR /WebApp/ @@ -18,12 +19,9 @@ RUN yarn global add pm2 COPY package.json /WebApp/ COPY yarn.lock /WebApp/ -RUN yarn install --frozen-lockfile --non-interactive +RUN yarn install --production=false --frozen-lockfile --non-interactive RUN apk del build-dependencies -# must be after `yarn install` -ENV NODE_ENV=production - COPY . /WebApp/ -CMD ["pm2", "start", "node", "build/main.js", "-n", "frontend", "-i", "2", "--attach"] +CMD ["yarn", "run", "deploy"] diff --git a/docker-compose.override.yml b/docker-compose.override.yml new file mode 100644 index 00000000..3186f8a8 --- /dev/null +++ b/docker-compose.override.yml @@ -0,0 +1,8 @@ +version: '3' + +services: + webapp: + command: yarn run dev + volumes: + - .:/WebApp + - /WebApp/node_modules/ diff --git a/docker-compose.staging.yml b/docker-compose.staging.yml new file mode 100644 index 00000000..923bb598 --- /dev/null +++ b/docker-compose.staging.yml @@ -0,0 +1,71 @@ +version: '3' + +services: + webapp: + image: humanconnection/frontend-nuxt + networks: + hc-network: + ipv4_address: 172.25.0.20 + environment: + - NODE_ENV=staging + - WEBAPP_HOST=172.25.0.20 + - WEBAPP_BASE_URL=http://172.25.0.20:3000 + - API_HOST=172.25.0.11 + - WEBAPP_PORT=3000 + - API_PORT=3030 + - MAPBOX_TOKEN=pk.eyJ1IjoiaHVtYW4tY29ubmVjdGlvbiIsImEiOiJjajl0cnBubGoweTVlM3VwZ2lzNTNud3ZtIn0.KZ8KK9l70omjXbEkkbHGsQ + - SENTRY_DNS_PUBLIC= + - EMBED_API_URL= + # these secrets should only be accessible on the server: + - SENTRY_DNS_PRIVATE= + - EMBED_API_TOKEN= + depends_on: + - api + build: + args: + BUILD_COMMIT: ${BUILD_COMMIT} + mongo: + image: mongo + networks: + - hc-network + command: "--smallfiles --logpath=/dev/null" + api: + image: humanconnection/api-feathers + depends_on: + - maildev + - thumbor + - mongo + environment: + - NODE_ENV=staging + ports: + - "3030:3030" + stdin_open: true + tty: true + networks: + hc-network: + ipv4_address: 172.25.0.11 + thumbor: + networks: + - hc-network + maildev: + image: djfarrelly/maildev + networks: + - hc-network + ports: + - "1080:80" + - "1025:25" + thumbor: + image: apsl/thumbor + networks: + - hc-network + ports: + - "8000:8000" + +networks: + hc-network: + driver: bridge + ipam: + driver: default + config: + - + subnet: 172.25.0.0/16 diff --git a/docker-compose.yml b/docker-compose.yml index 6a7a6ef6..196c2c97 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,9 +7,7 @@ services: context: . args: BUILD_COMMIT: ${BUILD_COMMIT} + stdin_open: true + tty: true ports: - 3000:3000/tcp - command: yarn run dev - volumes: - - .:/WebApp - - /WebApp/node_modules/ From b568f5e496cd78d48b8c102f2b4e8114c4792de4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Sun, 19 Aug 2018 15:08:10 +0200 Subject: [PATCH 07/14] Various fixes: * .nuxt seems to be a build folder and should not be synced * load config into process.env for local development --- docker-compose.override.yml | 1 + server/index.js | 2 ++ 2 files changed, 3 insertions(+) diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 3186f8a8..20b864a8 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -6,3 +6,4 @@ services: volumes: - .:/WebApp - /WebApp/node_modules/ + - /WebApp/.nuxt/ diff --git a/server/index.js b/server/index.js index bc854d93..28dfcf9d 100644 --- a/server/index.js +++ b/server/index.js @@ -10,6 +10,8 @@ import avatar from './avatar' import embeds from './embeds' import raven from '../plugins/raven-server' +require('dotenv').config() // load .env into process.env + const app = express() // Import and Set Nuxt.js options From f96a0d91633759652b2289778517c1ce6a1e9c0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Sun, 19 Aug 2018 15:27:29 +0200 Subject: [PATCH 08/14] Prefix yarn scripts based on purpose in docker ... and remove redundancy in docker-compose files --- Dockerfile | 2 +- docker-compose.override.yml | 2 +- docker-compose.staging.yml | 4 ---- package.json | 3 +-- 4 files changed, 3 insertions(+), 8 deletions(-) diff --git a/Dockerfile b/Dockerfile index c8c3c765..c8e6138f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,4 +24,4 @@ RUN yarn install --production=false --frozen-lockfile --non-interactive RUN apk del build-dependencies COPY . /WebApp/ -CMD ["yarn", "run", "deploy"] +CMD ["yarn", "run", "docker:cmd", "-i", "2", "--attach"] diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 20b864a8..aa534502 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -2,7 +2,7 @@ version: '3' services: webapp: - command: yarn run dev + command: sh -c "cp .env.example .env && yarn run dev" volumes: - .:/WebApp - /WebApp/node_modules/ diff --git a/docker-compose.staging.yml b/docker-compose.staging.yml index 923bb598..fbd82a86 100644 --- a/docker-compose.staging.yml +++ b/docker-compose.staging.yml @@ -2,7 +2,6 @@ version: '3' services: webapp: - image: humanconnection/frontend-nuxt networks: hc-network: ipv4_address: 172.25.0.20 @@ -21,9 +20,6 @@ services: - EMBED_API_TOKEN= depends_on: - api - build: - args: - BUILD_COMMIT: ${BUILD_COMMIT} mongo: image: mongo networks: diff --git a/package.json b/package.json index 3e6ac1b6..5cc4625c 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,7 @@ "envsub": "envsub .env.template .env", "build": "nuxt build && backpack build", "start": "node build/main.js", - "start:pm2": "pm2 start node build/main.js -n frontend -i 2 --attach", - "deploy": "yarn run envsub && yarn run build && yarn run start:pm2", + "docker:cmd": "yarn run envsub && yarn run build && pm2 start node build/main.js -n frontend", "precommit": "yarn eslint", "eslint": "eslint --ext .js,.vue .", "styleguide": "vue-styleguidist server", From b3467d5b5ec18ed946047c151ffe749a7566405f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Sun, 19 Aug 2018 16:06:50 +0200 Subject: [PATCH 09/14] Remove docker-compose.override.yml With server side rendering it is simply not possible to run the webapp without a bridged network --- docker-compose.override.yml | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 docker-compose.override.yml diff --git a/docker-compose.override.yml b/docker-compose.override.yml deleted file mode 100644 index aa534502..00000000 --- a/docker-compose.override.yml +++ /dev/null @@ -1,9 +0,0 @@ -version: '3' - -services: - webapp: - command: sh -c "cp .env.example .env && yarn run dev" - volumes: - - .:/WebApp - - /WebApp/node_modules/ - - /WebApp/.nuxt/ From 0d88ca78c0fee57646767e829f2a4c046358ba96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Fri, 31 Aug 2018 18:40:18 +0200 Subject: [PATCH 10/14] Make docker-compose.staging.yml default override The point of this docker-compose file is to give a running *local* environment. It is not complete up to this point. Eventually we could share this configuration with our staging system online. Also this commit passes secret environment variables from host system to container. --- ...compose.staging.yml => docker-compose.override.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) rename docker-compose.staging.yml => docker-compose.override.yml (84%) diff --git a/docker-compose.staging.yml b/docker-compose.override.yml similarity index 84% rename from docker-compose.staging.yml rename to docker-compose.override.yml index fbd82a86..db7d549d 100644 --- a/docker-compose.staging.yml +++ b/docker-compose.override.yml @@ -6,18 +6,19 @@ services: hc-network: ipv4_address: 172.25.0.20 environment: - - NODE_ENV=staging + - NODE_ENV=development + - MAINTENANCE=${MAINTENANCE} - WEBAPP_HOST=172.25.0.20 - WEBAPP_BASE_URL=http://172.25.0.20:3000 - API_HOST=172.25.0.11 - WEBAPP_PORT=3000 - API_PORT=3030 - MAPBOX_TOKEN=pk.eyJ1IjoiaHVtYW4tY29ubmVjdGlvbiIsImEiOiJjajl0cnBubGoweTVlM3VwZ2lzNTNud3ZtIn0.KZ8KK9l70omjXbEkkbHGsQ - - SENTRY_DNS_PUBLIC= - - EMBED_API_URL= + - SENTRY_DNS_PUBLIC=${SENTRY_DNS_PUBLIC} + - EMBED_API_URL=${EMBED_API_URL} # these secrets should only be accessible on the server: - - SENTRY_DNS_PRIVATE= - - EMBED_API_TOKEN= + - SENTRY_DNS_PRIVATE=${SENTRY_DNS_PRIVATE} + - EMBED_API_TOKEN=${EMBED_API_TOKEN} depends_on: - api mongo: From f690338786687a513f19da086a262fb63b6c73ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Fri, 31 Aug 2018 19:24:17 +0200 Subject: [PATCH 11/14] Remove custom code to read process.env at runtime * Install nuxt-env * Replace env with app.$env when possible * Remove custom plugins/env.js --- layouts/blank.vue | 2 +- layouts/default.vue | 2 +- layouts/error.vue | 2 +- middleware/maintenance.js | 4 ++-- nuxt.config.js | 28 ++++++++++++++-------------- package.json | 1 + plugins/api.js | 10 +++++----- plugins/env.js | 13 ------------- plugins/i18n.js | 4 ++-- plugins/raven-client.js | 12 ++++++------ plugins/raven-server.js | 6 +++--- yarn.lock | 6 +++++- 12 files changed, 41 insertions(+), 49 deletions(-) delete mode 100644 plugins/env.js diff --git a/layouts/blank.vue b/layouts/blank.vue index 8b112a0d..36f24303 100644 --- a/layouts/blank.vue +++ b/layouts/blank.vue @@ -38,7 +38,7 @@ }, title: 'loading...' } - if (process.env.NODE_ENV === 'development') { + if (this.$env.NODE_ENV === 'development') { head.script = [ { src: 'https://cdn.jsdelivr.net/npm/tota11y@0.1.6/build/tota11y.min.js' } ] diff --git a/layouts/default.vue b/layouts/default.vue index ae9822a7..5b39ab31 100644 --- a/layouts/default.vue +++ b/layouts/default.vue @@ -31,7 +31,7 @@ }, title: 'loading...' } - if (process.env.NODE_ENV === 'development') { + if (this.$env.NODE_ENV === 'development') { head.script = [ { src: 'https://cdn.jsdelivr.net/npm/tota11y@0.1.6/build/tota11y.min.js' } ] diff --git a/layouts/error.vue b/layouts/error.vue index bc03d25b..d7dcf19c 100644 --- a/layouts/error.vue +++ b/layouts/error.vue @@ -38,7 +38,7 @@ } }, mounted () { - if (process.env.NODE_ENV === 'development') { + if (this.$env.NODE_ENV === 'development') { console.error(this.error.message) } diff --git a/middleware/maintenance.js b/middleware/maintenance.js index da242d2f..c1b4c073 100644 --- a/middleware/maintenance.js +++ b/middleware/maintenance.js @@ -1,7 +1,7 @@ -export default async function ({ app, env, error, store, redirect, route }) { +export default async function ({ app, error, store, redirect, route }) { let isMaintenanceEnabled = false - if (Boolean(env.MAINTENANCE) === true) { + if (Boolean(app.$env.MAINTENANCE) === true) { error({ statusCode: 503, message: 'Maintenance Mode' }) return } diff --git a/nuxt.config.js b/nuxt.config.js index e01ca888..8a491ebd 100644 --- a/nuxt.config.js +++ b/nuxt.config.js @@ -1,4 +1,16 @@ const path = require('path') +const envWhitelist = [ + 'NODE_ENV', + 'WEBAPP_HOST', + 'WEBAPP_PORT', + 'WEBAPP_BASE_URL', + 'API_HOST', + 'API_PORT', + 'EMBED_API_URL', + 'SENTRY_DNS_PUBLIC', + 'MAPBOX_TOKEN', + 'MAINTENANCE' +] module.exports = { env: { @@ -92,7 +104,6 @@ module.exports = { } }, plugins: [ - {src: '~/plugins/env.js'}, {src: '~/plugins/debug.js', ssr: false}, {src: '~/plugins/raven-client.js', ssr: false}, {src: '~/plugins/api.js'}, @@ -111,19 +122,8 @@ module.exports = { {src: '~/plugins/open-page-in-modal.js', ssr: false} ], modules: [ - ['@nuxtjs/dotenv', { - only: [ - 'WEBAPP_HOST', - 'WEBAPP_PORT', - 'WEBAPP_BASE_URL', - 'API_HOST', - 'API_PORT', - 'EMBED_API_URL', - 'SENTRY_DNS_PUBLIC', - 'MAPBOX_TOKEN', - 'MAINTENANCE' - ] - }], + ['@nuxtjs/dotenv', { only: envWhitelist }], + ['nuxt-env', { keys: envWhitelist }], 'cookie-universal-nuxt' // '@nuxtjs/pwa' ], diff --git a/package.json b/package.json index 0b90c469..dd660743 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "mapbox-gl": "~0.45.0", "moment": "^2.22.2", "nuxt": "~1.4.0", + "nuxt-env": "^0.0.3", "parchment": "^1.1.4", "quill": "^1.3.6", "quill-delta": "^3.6.2", diff --git a/plugins/api.js b/plugins/api.js index 9794ac34..4619ef7f 100644 --- a/plugins/api.js +++ b/plugins/api.js @@ -5,9 +5,9 @@ import authentication from '@feathersjs/authentication-client' import urlHelper from '~/helpers/urls' import Vue from 'vue' -export default ({app, env, store, redirect, router}) => { +export default ({app, store, redirect, router}) => { const authKey = 'feathers-jwt' - const endpoint = urlHelper.buildEndpointURL(env.API_HOST, { port: env.API_PORT }) + const endpoint = urlHelper.buildEndpointURL(app.$env.API_HOST, { port: app.$env.API_PORT }) const storage = { getItem: (key) => { const res = app.$cookies.get(key) @@ -26,7 +26,7 @@ export default ({app, env, store, redirect, router}) => { }, clear: () => { const res = app.$cookies.removeAll() - if (env.NODE_ENV === 'development') { + if (app.$env.NODE_ENV === 'development') { console.log(`## STORAGE: clear()`, res) } return res @@ -60,7 +60,7 @@ export default ({app, env, store, redirect, router}) => { all: [ async (hook) => { // hook.accessToken = await api.passport.getJWT() - if (env.NODE_ENV === 'development') { + if (app.$env.NODE_ENV === 'development') { console.log('# API:', `${hook.method} ${hook.path}`) console.info('data', hook.data) // console.log('# ' + hook.accessToken) @@ -70,7 +70,7 @@ export default ({app, env, store, redirect, router}) => { ] }, async error (ctx) { - if (env.NODE_ENV === 'development') { + if (app.$env.NODE_ENV === 'development') { console.log('####################') console.error(ctx.error) console.info('JWT TOKEN: ', app.$cookies.get(authKey)) diff --git a/plugins/env.js b/plugins/env.js deleted file mode 100644 index d2081cc2..00000000 --- a/plugins/env.js +++ /dev/null @@ -1,13 +0,0 @@ -import Vue from 'vue' - -export default async (context) => { - context.app.$env = context.env - - Vue.use({ - install (Vue, store) { - Vue.prototype.$env = context.app.$env - } - }, context.store) - - return context.app.$env -} diff --git a/plugins/i18n.js b/plugins/i18n.js index 7b4dd790..b581e0ea 100644 --- a/plugins/i18n.js +++ b/plugins/i18n.js @@ -4,8 +4,8 @@ import { debounce, isEmpty } from 'lodash' import vuexI18n from 'vuex-i18n/dist/vuex-i18n.umd.js' -export default ({ app, env, req, cookie, store }) => { - const doDebug = env.NODE_ENV !== 'production' +export default ({ app, req, cookie, store }) => { + const doDebug = app.$env.NODE_ENV !== 'production' const key = 'locale' const changeHandler = debounce((mutation, store) => { diff --git a/plugins/raven-client.js b/plugins/raven-client.js index 0b3cc1dd..7508d33c 100644 --- a/plugins/raven-client.js +++ b/plugins/raven-client.js @@ -2,14 +2,14 @@ import Vue from 'vue' import Raven from 'raven-js' import RavenVue from 'raven-js/plugins/vue' -export default ({env}) => { - if (process.browser && env.SENTRY_DNS_PUBLIC) { +export default ({app}) => { + if (process.browser && app.$env.SENTRY_DNS_PUBLIC) { Raven - .config(env.SENTRY_DNS_PUBLIC, { - release: env.BUILD_DATE, - environment: env.NODE_ENV, + .config(app.$env.SENTRY_DNS_PUBLIC, { + release: app.$env.BUILD_DATE, + environment: app.$env.NODE_ENV, tags: { - deployed: env.DEPLOY_DATE, + deployed: app.$env.DEPLOY_DATE, client: true } }) diff --git a/plugins/raven-server.js b/plugins/raven-server.js index 6174a99e..f8d11ed2 100644 --- a/plugins/raven-server.js +++ b/plugins/raven-server.js @@ -5,10 +5,10 @@ export default async function (app) { // LOGGING IS ENABLED console.log('SENTRY LOGGING IS ENABLED') Raven.config(process.env.SENTRY_DNS_PRIVATE, { - release: process.env.BUILD_COMMIT, - environment: process.env.NODE_ENV, + release: app.$env.BUILD_COMMIT, + environment: app.$env.NODE_ENV, tags: { - deployed: process.env.DEPLOY_DATE, + deployed: app.$env.DEPLOY_DATE, client: true } }).install() diff --git a/yarn.lock b/yarn.lock index d47e9e79..72f11cb9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -252,7 +252,7 @@ "@nuxtjs/dotenv@^1.1.1": version "1.1.1" - resolved "https://registry.yarnpkg.com/@nuxtjs/dotenv/-/dotenv-1.1.1.tgz#c3d68f96d8f76cac4c4ddbc8268702eb95737bbe" + resolved "http://registry.npmjs.org/@nuxtjs/dotenv/-/dotenv-1.1.1.tgz#c3d68f96d8f76cac4c4ddbc8268702eb95737bbe" dependencies: dotenv "^5.0.0" @@ -7022,6 +7022,10 @@ number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" +nuxt-env@^0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/nuxt-env/-/nuxt-env-0.0.3.tgz#fcd1a614c95fa0ed61adeac37d1d5e1107a57b78" + nuxt@~1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/nuxt/-/nuxt-1.4.0.tgz#66df9ede68daf2ef4cfbcc329da7dda0003e0420" From ddc215497cde19aa18b31062f6c7b6dd09a71042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Fri, 31 Aug 2018 20:05:40 +0200 Subject: [PATCH 12/14] Don't rebuild the app at runtime * add yarn run build to Dockerfile * remove envsub (now unnecessary) - all configurations are controlled via env variables passed from host to container * remove .env.template --- .env.template | 18 ------------------ Dockerfile | 3 ++- package.json | 3 --- yarn.lock | 19 ++----------------- 4 files changed, 4 insertions(+), 39 deletions(-) delete mode 100644 .env.template diff --git a/.env.template b/.env.template deleted file mode 100644 index 25d99134..00000000 --- a/.env.template +++ /dev/null @@ -1,18 +0,0 @@ -# WEBAPP -WEBAPP_HOST = ${WEBAPP_HOST} -WEBAPP_PORT = ${WEBAPP_PORT} -WEBAPP_BASE_URL = ${WEBAPP_BASE_URL} - -# API -API_HOST = ${API_HOST} -API_PORT = ${API_PORT} - -# METAAPI -EMBED_API_URL = ${EMBED_API_URL} - -# 3rd party integrations -SENTRY_DNS_PUBLIC = ${SENTRY_DNS_PUBLIC} -MAPBOX_TOKEN = ${MAPBOX_TOKEN} - -# MAINTENANCE -MAINTENANCE = ${MAINTENANCE} diff --git a/Dockerfile b/Dockerfile index c8e6138f..ff14ac13 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,4 +24,5 @@ RUN yarn install --production=false --frozen-lockfile --non-interactive RUN apk del build-dependencies COPY . /WebApp/ -CMD ["yarn", "run", "docker:cmd", "-i", "2", "--attach"] +RUN ["yarn", "run", "build"] +CMD ["pm2", "start", "node", "build/main.js", "-n", "frontend", "-i", "2", "--attach"] diff --git a/package.json b/package.json index dd660743..b2dc7416 100644 --- a/package.json +++ b/package.json @@ -31,10 +31,8 @@ "clean": "rm -rf build/*", "dev": "backpack dev", "dev:debug": "backpack dev --inspect=0.0.0.0:9229", - "envsub": "envsub .env.template .env", "build": "nuxt build && backpack build", "start": "node build/main.js", - "docker:cmd": "yarn run envsub && yarn run build && pm2 start node build/main.js -n frontend", "precommit": "yarn eslint", "eslint": "eslint --ext .js,.vue .", "styleguide": "vue-styleguidist server", @@ -67,7 +65,6 @@ "css-loader": "~0.28.10", "csv-parse": "~2.4.0", "emitter-js": "~2.0.1", - "envsub": "~3.1.0", "express": "~4.16.2", "express-healthcheck": "~0.1.0", "express-locale": "~1.0.5", diff --git a/yarn.lock b/yarn.lock index 72f11cb9..d1523aef 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1648,7 +1648,7 @@ block-stream@*: dependencies: inherits "~2.0.0" -bluebird@^3.0.0, bluebird@^3.1.1, bluebird@^3.4.7, bluebird@^3.5.0, bluebird@^3.5.1: +bluebird@^3.0.0, bluebird@^3.1.1, bluebird@^3.4.7, bluebird@^3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" @@ -3167,10 +3167,6 @@ detect-port-alt@1.1.6: address "^1.0.1" debug "^2.6.0" -diff@^3.2.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" - diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" @@ -3454,17 +3450,6 @@ envify@^4.0.0: esprima "^4.0.0" through "~2.3.4" -envsub@~3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/envsub/-/envsub-3.1.0.tgz#06a9ef032d026771018335ff8d15e53bdd10cc22" - dependencies: - bluebird "^3.5.0" - chalk "^1.1.3" - commander "^2.9.0" - diff "^3.2.0" - handlebars "^4.0.6" - lodash "^4.17.4" - equal-length@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/equal-length/-/equal-length-1.0.1.tgz#21ca112d48ab24b4e1e7ffc0e5339d31fdfc274c" @@ -4702,7 +4687,7 @@ handle-thing@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4" -handlebars@^4.0.1, handlebars@^4.0.6: +handlebars@^4.0.1: version "4.0.11" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc" dependencies: From b887b2b8dad60c7d1317d4c1cdc4ae0005c01224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Wed, 5 Sep 2018 11:17:14 +0200 Subject: [PATCH 13/14] Implement feathers-vuex --- helpers/createApiClient.js | 65 +++++ middleware/auth.js | 8 + package.json | 7 +- plugins/api.js | 69 +---- store/auth.js | 462 ++++++++++++++++----------------- store/categories.js | 110 ++++---- store/comments.js | 168 ++++++------ store/connections.js | 183 ++++++------- store/index.js | 72 ++++- store/layout.js | 73 +++--- store/newsfeed.js | 347 +++++++++++++------------ store/notifications.js | 417 ++++++++++++++--------------- store/organizations.js | 33 +-- store/search.js | 120 ++++----- store/services/users.js | 11 + store/services/usersettings.js | 11 + store/settings.js | 130 +++++----- store/usersettings.js | 31 ++- yarn.lock | 114 ++++---- 19 files changed, 1283 insertions(+), 1148 deletions(-) create mode 100644 helpers/createApiClient.js create mode 100644 middleware/auth.js create mode 100644 store/services/users.js create mode 100644 store/services/usersettings.js diff --git a/helpers/createApiClient.js b/helpers/createApiClient.js new file mode 100644 index 00000000..87c50ead --- /dev/null +++ b/helpers/createApiClient.js @@ -0,0 +1,65 @@ +import feathers from '@feathersjs/feathers' +import socketio from '@feathersjs/socketio-client' +import io from 'socket.io-client' +import authentication from '@feathersjs/authentication-client' +import urlHelper from '~/helpers/urls' +import Cookie from 'cookie-universal' + +const authKey = 'feathers-jwt' +const endpoint = urlHelper.buildEndpointURL(process.env.API_HOST, { port: process.env.API_PORT }) +let socket +if (process.env.ENV === 'production') { + socket = socketio(io(endpoint), { timeout: 20000 }) + if (process.server) { + setTimeout(() => { + // close server connection as content was delivered already after 30 seconds at latest + try { + socket.close() + } catch (err) { + console.log(err) + } + }, 30000) + } +} else { + socket = socketio(io(endpoint)) +} + +let createApiClient = ({req, res}) => { + const cookies = Cookie(req, res) + const storageMapping = { + getItem: (key) => { + const res = cookies.get(key) + // console.log(`## STORAGE: getItem(${key})`, res) + return res + }, + setItem: (key, value, options) => { + const res = cookies.set(key, value, options) + // console.log(`## STORAGE: setItem(${key}, ${value}, ${options})`, res) + return res + }, + removeItem: (key) => { + const res = cookies.remove(key) + // console.log(`## STORAGE: removeItem(${key})`, res) + return res + }, + clear: () => { + const res = cookies.removeAll() + if (process.env.NODE_ENV === 'development') { + console.log(`## STORAGE: clear()`, res) + } + return res + } + } + + let api = feathers() + .configure(socket) + .configure(authentication({ + storage: storageMapping, + storageKey: authKey, + cookie: authKey + })) + + return api +} + +export default createApiClient diff --git a/middleware/auth.js b/middleware/auth.js new file mode 100644 index 00000000..fe972f56 --- /dev/null +++ b/middleware/auth.js @@ -0,0 +1,8 @@ +// If it's a private page and there's no payload, redirect. +export default function (context) { + const { store, redirect, route } = context + const { auth } = store.state + if (!auth.publicPages.includes(route.name) && !auth.payload) { + return redirect('/auth/login') + } +} diff --git a/package.json b/package.json index b2dc7416..74c87be7 100644 --- a/package.json +++ b/package.json @@ -51,15 +51,18 @@ "@nuxtjs/pwa": "~2.0.5", "axios": "^0.18.0", "babel-eslint": "~8.2.5", + "babel-plugin-transform-object-rest-spread": "^6.26.0", "babel-preset-vue-app": "~2.0.0", "body-parser": "~1.18.3", "body-scroll-lock": "^2.4.6", "bricks.js": "~1.8.0", + "browser-env": "^3.2.5", "buefy": "~0.6.5", "bulma": "~0.7.1", "bulma-steps-component": "^0.5.3", "cheerio": "^1.0.0-rc.2", "cookie-parser": "~1.4.3", + "cookie-universal": "^2.0.5", "cookie-universal-nuxt": "~2.0.3", "cross-env": "~5.2.0", "css-loader": "~0.28.10", @@ -69,6 +72,7 @@ "express-healthcheck": "~0.1.0", "express-locale": "~1.0.5", "express-session": "~1.15.6", + "feathers-vuex": "^1.4.8", "font-awesome": "~4.7.0", "helmet": "^3.12.1", "i18n-iso-countries": "^3.6.2", @@ -108,6 +112,7 @@ "vue-server-renderer": "~2.5.16", "vue-styleguidist": "~1.7.7", "vue-template-compiler": "~2.5.16", + "vue-test-utils": "^1.0.0-beta.11", "vue-text-glitch": "^1.0.1", "vuelidate": "~0.6.1", "vuex-i18n": "~1.10.5", @@ -119,7 +124,6 @@ "@vue/test-utils": "^1.0.0-beta.20", "ava": "~0.25.0", "backpack-core": "~0.7.0", - "browser-env": "~3.2.4", "codecov": "~3.0.4", "eslint": "~4.19.1", "eslint-config-standard": "~11.0.0-beta.0", @@ -131,7 +135,6 @@ "eslint-plugin-standard": "~3.1.0", "eslint-plugin-vue": "^4.5.0", "istanbul": "~0.4.5", - "jsdom": "~11.10.0", "less": "~2.7.3", "less-loader": "^4.1.0", "node-sass": "~4.9.0", diff --git a/plugins/api.js b/plugins/api.js index 4619ef7f..a785344d 100644 --- a/plugins/api.js +++ b/plugins/api.js @@ -1,60 +1,15 @@ -import feathers from '@feathersjs/feathers' -import socketio from '@feathersjs/socketio-client' -import io from 'socket.io-client' -import authentication from '@feathersjs/authentication-client' -import urlHelper from '~/helpers/urls' +import feathersVuex from 'feathers-vuex' import Vue from 'vue' +import Vuex from 'vuex' +import createApiClient from '../helpers/createApiClient' -export default ({app, store, redirect, router}) => { - const authKey = 'feathers-jwt' - const endpoint = urlHelper.buildEndpointURL(app.$env.API_HOST, { port: app.$env.API_PORT }) - const storage = { - getItem: (key) => { - const res = app.$cookies.get(key) - // console.log(`## STORAGE: getItem(${key})`, res) - return res - }, - setItem: (key, value, options) => { - const res = app.$cookies.set(key, value, options) - // console.log(`## STORAGE: setItem(${key}, ${value}, ${options})`, res) - return res - }, - removeItem: (key) => { - const res = app.$cookies.remove(key) - // console.log(`## STORAGE: removeItem(${key})`, res) - return res - }, - clear: () => { - const res = app.$cookies.removeAll() - if (app.$env.NODE_ENV === 'development') { - console.log(`## STORAGE: clear()`, res) - } - return res - } - } +export default ({app, store, redirect, router, req, res}) => { + const api = createApiClient({req, res}) + const { FeathersVuex } = feathersVuex(api, { idField: '_id' }) - const socket = io(endpoint) + Vue.use(FeathersVuex) + Vue.use(Vuex) - if (process.server) { - setTimeout(() => { - // close server connection as content was delivered already after 30 seconds at latest - try { - socket.close() - } catch (err) { - app.error(err) - } - }, 30000) - } - - let api = feathers() - .configure(socketio(socket, { - timeout: 20000 - })) - .configure(authentication({ - storage: storage, - storageKey: authKey, - cookie: authKey - })) api.hooks({ before: { all: [ @@ -73,7 +28,7 @@ export default ({app, store, redirect, router}) => { if (app.$env.NODE_ENV === 'development') { console.log('####################') console.error(ctx.error) - console.info('JWT TOKEN: ', app.$cookies.get(authKey)) + // console.info('JWT TOKEN: ', app.$cookies.get(authKey)) console.info('path', ctx.path) console.info('service', ctx.service) console.info('method', ctx.method) @@ -103,7 +58,6 @@ export default ({app, store, redirect, router}) => { // fix issues where we could not log in // when at development await api.passport.logout() - storage.removeItem(authKey) } let user = null const response = await api.authenticate(options) @@ -120,11 +74,6 @@ export default ({app, store, redirect, router}) => { api.channel('authenticated').join(connection) }) - /** - * @deprecated - */ - api.authKey = authKey - // make the api accessible inside vue components Vue.use({ install (Vue) { diff --git a/store/auth.js b/store/auth.js index 606b8ba6..69795d09 100644 --- a/store/auth.js +++ b/store/auth.js @@ -1,265 +1,263 @@ import { isEmpty } from 'lodash' -export const state = () => { - return { +export default { + namespaced: true, + state: { user: null, token: null - } -} - -export const mutations = { - SET_USER (state, user) { - state.user = user || null }, - SET_USER_SETTINGS (state, userSettings) { - state.user = Object.assign(state.user, { - userSettings: Object.assign(this.getters['auth/userSettings'], userSettings) - }) + mutations: { + SET_USER (state, user) { + state.user = user || null + }, + SET_USER_SETTINGS (state, userSettings) { + state.user = Object.assign(state.user, { + userSettings: Object.assign(this.getters['auth/userSettings'], userSettings) + }) + }, + SET_TOKEN (state, token) { + state.token = token || null + } }, - SET_TOKEN (state, token) { - state.token = token || null - } -} + getters: { + isAuthenticated (state) { + return !!(state.user && state.token) + }, + isVerified (state) { + return !!state.user && state.user.isVerified && !!state.user.name + }, + isAdmin (state) { + return !!state.user && state.user.role === 'admin' + }, + isModerator (state) { + return !!state.user && (state.user.role === 'admin' || state.user.role === 'moderator') + }, + user (state) { + return state.user + }, + token (state) { + return state.token + }, + userSettings (state, getters, rootState, rootGetters) { + const userSettings = (state.user && state.user.userSettings) ? state.user.userSettings : {} -export const getters = { - isAuthenticated (state) { - return !!(state.user && state.token) - }, - isVerified (state) { - return !!state.user && state.user.isVerified && !!state.user.name - }, - isAdmin (state) { - return !!state.user && state.user.role === 'admin' - }, - isModerator (state) { - return !!state.user && (state.user.role === 'admin' || state.user.role === 'moderator') - }, - user (state) { - return state.user - }, - token (state) { - return state.token - }, - userSettings (state, getters, rootState, rootGetters) { - const userSettings = (state.user && state.user.userSettings) ? state.user.userSettings : {} + const defaultLanguage = (state.user && state.user.language) ? state.user.language : rootGetters['i18n/locale'] + let contentLanguages = !isEmpty(userSettings.contentLanguages) ? userSettings.contentLanguages : [] + if (isEmpty(contentLanguages)) { + contentLanguages = userSettings.uiLanguage ? [userSettings.uiLanguage] : [defaultLanguage] + } - const defaultLanguage = (state.user && state.user.language) ? state.user.language : rootGetters['i18n/locale'] - let contentLanguages = !isEmpty(userSettings.contentLanguages) ? userSettings.contentLanguages : [] - if (isEmpty(contentLanguages)) { - contentLanguages = userSettings.uiLanguage ? [userSettings.uiLanguage] : [defaultLanguage] + return Object.assign({ + uiLanguage: defaultLanguage, + contentLanguages: contentLanguages + }, userSettings) } + }, + actions: { + async init ({commit, dispatch, state}) { + let user + // get fresh jwt token + await dispatch('refreshJWT', 'auth/init') - return Object.assign({ - uiLanguage: defaultLanguage, - contentLanguages: contentLanguages - }, userSettings) - } -} + // check if the token is authenticated + const isAuthenticated = await dispatch('checkAuth') + if (isAuthenticated) { + try { + const payload = await this.app.$api.passport.verifyJWT(state.token) + user = await this.app.$api.service('users').get(payload.userId) + } catch (err) { + user = null + } + commit('SET_USER', user) + } + return user + }, + async refreshJWT ({state, getters, commit, dispatch}, source = null) { + const token = await this.app.$api.passport.getJWT() + commit('SET_TOKEN', token) -export const actions = { - async init ({commit, dispatch, state}) { - let user - // get fresh jwt token - await dispatch('refreshJWT', 'auth/init') + if (process.env.NODE_ENV === 'development') { + console.log('### refreshJWT', source, !isEmpty(token), getters.isAuthenticated) + } - // check if the token is authenticated - const isAuthenticated = await dispatch('checkAuth') - if (isAuthenticated) { - try { - const payload = await this.app.$api.passport.verifyJWT(state.token) - user = await this.app.$api.service('users').get(payload.userId) - } catch (err) { - user = null + let user + if (token) { + try { + user = await this.app.$api.auth({strategy: 'jwt', accessToken: token}) + } catch (err) { + user = null + } + commit('SET_USER', user) + } + return user + }, + async checkAuth ({state, getters, commit, dispatch}) { + if (!getters.user) { + commit('SET_USER', null) + commit('SET_TOKEN', null) + return false } - commit('SET_USER', user) - } - return user - }, - async refreshJWT ({state, getters, commit, dispatch}, source = null) { - const token = await this.app.$api.passport.getJWT() - commit('SET_TOKEN', token) - if (process.env.NODE_ENV === 'development') { - console.log('### refreshJWT', source, !isEmpty(token), getters.isAuthenticated) - } + const token = await this.app.$api.passport.getJWT() + commit('SET_TOKEN', token) - let user - if (token) { + if (!token) { + return false + } + let payloadValid = false try { - user = await this.app.$api.auth({strategy: 'jwt', accessToken: token}) + const payload = await this.app.$api.passport.verifyJWT(token) + payloadValid = this.app.$api.passport.payloadIsValid(payload) } catch (err) { - user = null + payloadValid = false + } + if (payloadValid) { + commit('SET_TOKEN', token) + } else { + commit('SET_USER', null) + commit('SET_TOKEN', null) } - commit('SET_USER', user) - } - return user - }, - async checkAuth ({state, getters, commit, dispatch}) { - if (!getters.user) { - commit('SET_USER', null) - commit('SET_TOKEN', null) - return false - } - - const token = await this.app.$api.passport.getJWT() - commit('SET_TOKEN', token) - - if (!token) { - return false - } - let payloadValid = false - try { - const payload = await this.app.$api.passport.verifyJWT(token) - payloadValid = this.app.$api.passport.payloadIsValid(payload) - } catch (err) { - payloadValid = false - } - if (payloadValid) { - commit('SET_TOKEN', token) - } else { - commit('SET_USER', null) - commit('SET_TOKEN', null) - } - return payloadValid === true - }, - async login ({commit, dispatch, getters}, {email, password}) { - try { - commit('SET_USER', null) - commit('SET_TOKEN', null) - const user = await this.app.$api.auth({strategy: 'local', email, password}) + return payloadValid === true + }, + async login ({commit, dispatch, getters}, {email, password}) { + try { + commit('SET_USER', null) + commit('SET_TOKEN', null) + const user = await this.app.$api.auth({strategy: 'local', email, password}) - commit('SET_USER', user) + commit('SET_USER', user) - try { - const waitForUserSettings = () => { - return new Promise(resolve => { - setTimeout(() => { - resolve(getters.userSettings) - }, 250) - }) - } - const locale = this.app.$cookies.get('locale') - const userSettings = await waitForUserSettings() - if (!isEmpty(locale) && user && userSettings && userSettings.uiLanguage !== locale) { - // console.log('update user locale setting with cookie setting (had changed in on login screen)') - // update user locale setting with cookie setting (had changed in on login screen) - dispatch('usersettings/patch', { - uiLanguage: locale - }, { root: true }) - } else if (isEmpty(locale) && user && userSettings && userSettings.uiLanguage) { - // console.log('set locale to user setting and persist in cookie') - // set locale to user setting and persist in cookie - this.app.$cookies.set('locale', userSettings.uiLanguage) - this.app.$i18n.set(userSettings.uiLanguage) + try { + const waitForUserSettings = () => { + return new Promise(resolve => { + setTimeout(() => { + resolve(getters.userSettings) + }, 250) + }) + } + const locale = this.app.$cookies.get('locale') + const userSettings = await waitForUserSettings() + if (!isEmpty(locale) && user && userSettings && userSettings.uiLanguage !== locale) { + // console.log('update user locale setting with cookie setting (had changed in on login screen)') + // update user locale setting with cookie setting (had changed in on login screen) + dispatch('usersettings/patch', { + uiLanguage: locale + }, { root: true }) + } else if (isEmpty(locale) && user && userSettings && userSettings.uiLanguage) { + // console.log('set locale to user setting and persist in cookie') + // set locale to user setting and persist in cookie + this.app.$cookies.set('locale', userSettings.uiLanguage) + this.app.$i18n.set(userSettings.uiLanguage) + } + commit('newsfeed/clear', null, { root: true }) + } catch (err) { + console.error(err) } - commit('newsfeed/clear', null, { root: true }) + + return user } catch (err) { - console.error(err) + commit('SET_USER', null) + // commit('SET_TOKEN', null) + throw new Error(err.message) } - - return user - } catch (err) { - commit('SET_USER', null) - // commit('SET_TOKEN', null) - throw new Error(err.message) - } - }, - async logout ({commit}) { - this.app.router.push('/auth/logout') - }, - register ({dispatch, commit}, {email, password, inviteCode, invitedByUserId}) { - return this.app.$api.service('users').create({email, password, inviteCode, invitedByUserId, termsAndConditionsAccepted: new Date()}) - .then(response => { - return dispatch('login', {email, password}) - }) - }, - async patch ({state, commit, dispatch}, data) { - let user = state.user - // console.log('####################') - // console.log('#AUTH/PATCH', data) - // console.log('#USER ID', state.user._id) - // console.log('#JWT TOKEN', this.$cookies.get(this.app.$api.authKey)) - // console.log('#state.isAuthenticated', state.isAuthenticated) - // console.log('#state.use', state.use) - // console.log('####################') - // if (!state.isAuthenticated || !user) { - // user = await dispatch('refreshJWT') - // } - if (!user) { - // stop when the user is not authenticated - console.error('stop when the user is not authenticated') - throw new Error('NO USER') - } - user = await this.app.$api.service('users').patch(user._id, data) - commit('SET_USER', user) - return user - }, - async refreshUser ({state, commit}, userSettings) { - if (state.user && userSettings) { - commit('SET_USER_SETTINGS', userSettings) - } else if (state.user) { - const user = await this.app.$api.service('users').get(state.user._id) + }, + async logout ({commit}) { + this.app.router.push('/auth/logout') + }, + register ({dispatch, commit}, {email, password, inviteCode, invitedByUserId}) { + return this.app.$api.service('users').create({email, password, inviteCode, invitedByUserId, termsAndConditionsAccepted: new Date()}) + .then(response => { + return dispatch('login', {email, password}) + }) + }, + async patch ({state, commit, dispatch}, data) { + let user = state.user + // console.log('####################') + // console.log('#AUTH/PATCH', data) + // console.log('#USER ID', state.user._id) + // console.log('#JWT TOKEN', this.$cookies.get(this.app.$api.authKey)) + // console.log('#state.isAuthenticated', state.isAuthenticated) + // console.log('#state.use', state.use) + // console.log('####################') + // if (!state.isAuthenticated || !user) { + // user = await dispatch('refreshJWT') + // } + if (!user) { + // stop when the user is not authenticated + console.error('stop when the user is not authenticated') + throw new Error('NO USER') + } + user = await this.app.$api.service('users').patch(user._id, data) commit('SET_USER', user) return user - } - return state.user - }, - verify ({dispatch}, verifyToken) { - if (!verifyToken) { return false } - return this.app.$api.service('authManagement').create({ - action: 'verifySignupLong', - value: verifyToken - }) - .then(() => { - return true - }) - .catch(err => { - console.error(err.message, err) - }) - }, - resendVerifySignup ({state, dispatch}) { - if (!state.user.email) { return false } - return this.app.$api.service('authManagement').create({ - action: 'resendVerifySignup', - value: { - email: state.user.email + }, + async refreshUser ({state, commit}, userSettings) { + if (state.user && userSettings) { + commit('SET_USER_SETTINGS', userSettings) + } else if (state.user) { + const user = await this.app.$api.service('users').get(state.user._id) + commit('SET_USER', user) + return user } - }) - .then(() => { - return true - }) - .catch(err => { - console.error(err.message, err) + return state.user + }, + verify ({dispatch}, verifyToken) { + if (!verifyToken) { return false } + return this.app.$api.service('authManagement').create({ + action: 'verifySignupLong', + value: verifyToken }) - }, - resetPassword ({state}, data) { - return this.app.$api.service('authManagement').create({ - action: 'sendResetPwd', - value: { - email: data.email - } - }) - .then(() => { - return true - }) - .catch(err => { - throw new Error(err.message) + .then(() => { + return true + }) + .catch(err => { + console.error(err.message, err) + }) + }, + resendVerifySignup ({state, dispatch}) { + if (!state.user.email) { return false } + return this.app.$api.service('authManagement').create({ + action: 'resendVerifySignup', + value: { + email: state.user.email + } }) - }, - setNewPassword ({state}, data) { - return this.app.$api.service('authManagement').create({ - action: 'resetPwdLong', - value: { - token: data.token, - password: data.password - } - }) - .then(() => { - return true + .then(() => { + return true + }) + .catch(err => { + console.error(err.message, err) + }) + }, + resetPassword ({state}, data) { + return this.app.$api.service('authManagement').create({ + action: 'sendResetPwd', + value: { + email: data.email + } }) - .catch(err => { - throw new Error(err.message) + .then(() => { + return true + }) + .catch(err => { + throw new Error(err.message) + }) + }, + setNewPassword ({state}, data) { + return this.app.$api.service('authManagement').create({ + action: 'resetPwdLong', + value: { + token: data.token, + password: data.password + } }) + .then(() => { + return true + }) + .catch(err => { + throw new Error(err.message) + }) + } } } diff --git a/store/categories.js b/store/categories.js index 37414ace..68c3b09e 100644 --- a/store/categories.js +++ b/store/categories.js @@ -1,66 +1,66 @@ import { castArray } from 'lodash' -export const state = () => { - return { +export default { + namespaced: true, + state: { categories: [] - } -} - -export const mutations = { - set (state, categories) { - state.categories = castArray(categories) }, - // [vuex] unknown local mutation type: clear, global type: categories/clear - clear (state) { - state.categories = [] - } -} - -export const getters = { - all (state) { - return state.categories - } -} - -export const actions = { - // Called from nuxtServerInit in index - init ({state, dispatch}) { - if (state.categories.length) { - // do not fetch again - return + mutations: { + set (state, categories) { + state.categories = castArray(categories) + }, + // [vuex] unknown local mutation type: clear, global type: categories/clear + clear (state) { + state.categories = [] } - return dispatch('fetch') }, - // Called from plugins/init-store-subscriptions only once - subscribe ({dispatch}) { - return this.app.$api.service('categories') - .on('created', () => { - dispatch('fetch') - }) + + getters: { + all (state) { + return state.categories + } }, - fetch ({commit}) { - return this.app.$api.service('categories').find({ - query: { - '$limit': 200, - '$sort': { - slug: 1 - } + + actions: { + // Called from nuxtServerInit in index + init ({state, dispatch}) { + if (state.categories.length) { + // do not fetch again + return } - }) - .then((result) => { - commit('set', result.data) - }) - .catch(() => { - commit('clear') + return dispatch('fetch') + }, + // Called from plugins/init-store-subscriptions only once + subscribe ({dispatch}) { + return this.app.$api.service('categories') + .on('created', () => { + dispatch('fetch') + }) + }, + fetch ({commit}) { + return this.app.$api.service('categories').find({ + query: { + '$limit': 200, + '$sort': { + slug: 1 + } + } }) - }, - create ({dispatch}, category) { - return this.app.$api.service('categories').create(category) - }, - patch ({dispatch}, category) { - return this.app.$api.service('categories').patch(category._id, category) - }, - delete ({dispatch}, category) { - return this.app.$api.service('categories').remove(category._id) + .then((result) => { + commit('set', result.data) + }) + .catch(() => { + commit('clear') + }) + }, + create ({dispatch}, category) { + return this.app.$api.service('categories').create(category) + }, + patch ({dispatch}, category) { + return this.app.$api.service('categories').patch(category._id, category) + }, + delete ({dispatch}, category) { + return this.app.$api.service('categories').remove(category._id) + } } } diff --git a/store/comments.js b/store/comments.js index 9d863a43..204768ae 100644 --- a/store/comments.js +++ b/store/comments.js @@ -1,98 +1,96 @@ import { castArray, debounce } from 'lodash' -export const state = () => { - return { +export default { + namespaced: true, + state: { comments: [], isLoading: true, contributionId: null - } -} - -export const mutations = { - isLoading (state, status) { - state.isLoading = status }, - set (state, comments) { - state.comments = castArray(comments) - }, - clear (state) { - state.comments = [] - }, - setContributionId (state, contributionId) { - state.contributionId = contributionId - } -} - -export const getters = { - all (state) { - return state.comments - }, - isLoading (state) { - return state.isLoading - }, - count (state) { - return state.comments.length - } -} - -export const actions = { - // Called from plugins/init-store-subscriptions only once - subscribe ({dispatch}) { - return this.app.$api.service('comments') - .on('created', debounce((comment) => { - dispatch('fetchByContributionId') - }, 500)) - .on('patched', debounce((comment) => { - dispatch('fetchByContributionId') - }, 500)) - .on('removed', debounce((comment) => { - dispatch('fetchByContributionId') - }, 500)) + mutations: { + isLoading (state, status) { + state.isLoading = status + }, + set (state, comments) { + state.comments = castArray(comments) + }, + clear (state) { + state.comments = [] + }, + setContributionId (state, contributionId) { + state.contributionId = contributionId + } }, - fetchByContributionId ({commit, state}, contributionId) { - contributionId = contributionId || state.contributionId - if (!contributionId) { - return + getters: { + all (state) { + return state.comments + }, + isLoading (state) { + return state.isLoading + }, + count (state) { + return state.comments.length } - commit('setContributionId', contributionId) - // TODO: implement pagination for comments - return this.app.$api.service('comments').find({ - query: { - contributionId: contributionId, - $sort: { - // upvoteCount: -1, - createdAt: 1 - }, - $limit: 100 + }, + actions: { + // Called from plugins/init-store-subscriptions only once + subscribe ({dispatch}) { + return this.app.$api.service('comments') + .on('created', debounce((comment) => { + dispatch('fetchByContributionId') + }, 500)) + .on('patched', debounce((comment) => { + dispatch('fetchByContributionId') + }, 500)) + .on('removed', debounce((comment) => { + dispatch('fetchByContributionId') + }, 500)) + }, + fetchByContributionId ({commit, state}, contributionId) { + contributionId = contributionId || state.contributionId + if (!contributionId) { + return } - }) - .then((result) => { - commit('set', result.data) - commit('isLoading', false) + commit('setContributionId', contributionId) + // TODO: implement pagination for comments + return this.app.$api.service('comments').find({ + query: { + contributionId: contributionId, + $sort: { + // upvoteCount: -1, + createdAt: 1 + }, + $limit: 100 + } }) - .catch((e) => { - commit('isLoading', false) + .then((result) => { + commit('set', result.data) + commit('isLoading', false) + }) + .catch((e) => { + commit('isLoading', false) + }) + }, + fetchById ({commit}, id) { + return this.app.$api.service('comments').get(id) + }, + upvote ({dispatch}, comment) { + return this.app.$api.service('comments').patch(comment._id, { + $inc: { + upvoteCount: 1 + } + }).then((res) => { + dispatch('fetchByContributionId', comment.contributionId) }) - }, - fetchById ({commit}, id) { - return this.app.$api.service('comments').get(id) - }, - upvote ({dispatch}, comment) { - return this.app.$api.service('comments').patch(comment._id, { - $inc: { - upvoteCount: 1 - } - }).then((res) => { - dispatch('fetchByContributionId', comment.contributionId) - }) - }, - create ({dispatch}, data) { - return this.app.$api.service('comments').create(data) - }, - patch ({dispatch}, data) { - return this.app.$api.service('comments').patch(data._id, data) - }, - remove ({dispatch}, id) { - return this.app.$api.service('comments').remove(id) + }, + create ({dispatch}, data) { + return this.app.$api.service('comments').create(data) + }, + patch ({dispatch}, data) { + return this.app.$api.service('comments').patch(data._id, data) + }, + remove ({dispatch}, id) { + return this.app.$api.service('comments').remove(id) + } } } diff --git a/store/connections.js b/store/connections.js index ee5beba9..6488fc30 100644 --- a/store/connections.js +++ b/store/connections.js @@ -1,5 +1,6 @@ -export const state = () => { - return { +export default { + namespaced: true, + state: { follow: { _id: null, userId: null, @@ -9,104 +10,104 @@ export const state = () => { isFollowing: false, count: 0 } - } -} - -export const mutations = { - follow (state, follow) { - state.follow = follow - } -} - -export const getters = { - follow (state) { - return state.follow - } -} + }, -export const actions = { - async syncFollow ({state, commit}, {userId, foreignId, foreignService}) { - let status = Object.assign({}, state.follow) + mutations: { + follow (state, follow) { + state.follow = follow + } + }, - if (status.userId !== userId) { - status.count = 0 + getters: { + follow (state) { + return state.follow } - status.userId = userId - status.foreignId = foreignId - status.foreignService = foreignService - status.isPending = true - commit('follow', status) - - status = Object.assign({}, state.follow) - - try { - const res = await this.app.$api.service('follows').find({ - query: { - userId, + }, + + actions: { + async syncFollow ({state, commit}, {userId, foreignId, foreignService}) { + let status = Object.assign({}, state.follow) + + if (status.userId !== userId) { + status.count = 0 + } + status.userId = userId + status.foreignId = foreignId + status.foreignService = foreignService + status.isPending = true + commit('follow', status) + + status = Object.assign({}, state.follow) + + try { + const res = await this.app.$api.service('follows').find({ + query: { + userId, + foreignId, + foreignService + } + }) + status.count = res.total || 0 + status._id = res.data.length ? res.data[0]._id : null + } catch (err) { + console.error(err) + status.count = 0 + } + + status.isFollowing = Boolean(status._id) + status.isPending = false + + commit('follow', status) + }, + async follow ({state, commit, dispatch}, {foreignId, foreignService}) { + let status = Object.assign({}, state.follow) + status.isPending = true + commit('follow', status) + + status = Object.assign({}, state.follow) + + try { + await this.app.$api.service('follows').create({ foreignId, foreignService - } - }) - status.count = res.total || 0 - status._id = res.data.length ? res.data[0]._id : null - } catch (err) { - console.error(err) - status.count = 0 - } + }) + } catch (err) {} - status.isFollowing = Boolean(status._id) - status.isPending = false + status.isFollowing = true + // status.isPending = false - commit('follow', status) - }, - async follow ({state, commit, dispatch}, {foreignId, foreignService}) { - let status = Object.assign({}, state.follow) - status.isPending = true - commit('follow', status) - - status = Object.assign({}, state.follow) + commit('follow', status) - try { - await this.app.$api.service('follows').create({ - foreignId, - foreignService + dispatch('syncFollow', { + userId: state.follow.userId, + foreignId: foreignId, + foreignService: foreignService }) - } catch (err) {} - - status.isFollowing = true - // status.isPending = false - - commit('follow', status) - - dispatch('syncFollow', { - userId: state.follow.userId, - foreignId: foreignId, - foreignService: foreignService - }) - }, - async unfollow ({state, commit, dispatch}, {_id}) { - let status = Object.assign({}, state.follow) - status.isPending = true - commit('follow', status) - - status = Object.assign({}, state.follow) - - try { - await this.app.$api.service('follows').remove({ - _id: _id + }, + async unfollow ({state, commit, dispatch}, {_id}) { + let status = Object.assign({}, state.follow) + status.isPending = true + commit('follow', status) + + status = Object.assign({}, state.follow) + + try { + await this.app.$api.service('follows').remove({ + _id: _id + }) + } catch (err) {} + + status.isFollowing = false + status.count-- + // status.isPending = false + status._id = null + commit('follow', status) + + dispatch('syncFollow', { + userId: state.follow.userId, + foreignId: state.follow.foreignId, + foreignService: state.follow.foreignService }) - } catch (err) {} - - status.isFollowing = false - status.count-- - // status.isPending = false - status._id = null - commit('follow', status) - - dispatch('syncFollow', { - userId: state.follow.userId, - foreignId: state.follow.foreignId, - foreignService: state.follow.foreignService - }) + } } } diff --git a/store/index.js b/store/index.js index ffcab2a1..fb5b10ae 100644 --- a/store/index.js +++ b/store/index.js @@ -1,7 +1,67 @@ -export const actions = { - async nuxtServerInit ({dispatch}) { - dispatch('categories/init') - await dispatch('auth/init') - await dispatch('settings/init') - } +import feathersVuex, { initAuth } from 'feathers-vuex' +import createApiClient from '../helpers/createApiClient' +import Vuex from 'vuex' + +import auth from './auth' +import categories from './categories' +import comments from './comments' +import connections from './connections' +import layout from './layout' +import newsfeed from './newsfeed' +import notifications from './notifications' +import organizations from './organizations' +import search from './search' +import settings from './settings' +import usersettings from './usersettings' + +const requireModule = require.context( + // The relative path holding the service modules + './services', + // Whether to look in subfolders + false, + // Only include .js files (prevents duplicate imports) + /.js$/ +) + +const createStore = (ssrContext) => { + const feathersClient = createApiClient(ssrContext || {}) + const { auth: feathersVuexAuthentication } = feathersVuex(feathersClient, { idField: '_id' }) + const servicePlugins = requireModule.keys().map(modulePath => requireModule(modulePath).default(feathersClient)) + + return new Vuex.Store({ + modules: { auth, categories, comments, connections, layout, newsfeed, notifications, organizations, search, settings, usersettings }, + actions: { + async nuxtServerInit ({dispatch, commit}, {req}) { + dispatch('categories/init') + await dispatch('auth/init') + await dispatch('settings/init') + return initAuth({ + commit, + dispatch, + req, + moduleName: 'auth', + cookieName: 'feathers-jwt' + }) + } + }, + plugins: [ + ...servicePlugins, + feathersVuexAuthentication({ + userService: 'users', + state: { + publicPages: [ + 'auth-login', + 'auth-register', + 'auth-signup', + 'auth-reset', + 'auth-reset-token', + 'pages-slug', + 'test' + ] + } + }) + ] + }) } + +export default createStore diff --git a/store/layout.js b/store/layout.js index d3e96e70..377d9bbd 100644 --- a/store/layout.js +++ b/store/layout.js @@ -1,47 +1,48 @@ -export const state = () => { - return { +export default { + namespaced: true, + state: { change: 0, sidebar: { open: false } - } -} - -export const mutations = { - CHANGE_LAYOUT (state) { - state.change++ - }, - CLOSE_SIDEBAR (state) { - state.sidebar.open = false }, - OPEN_SIDEBAR (state) { - state.sidebar.open = true - }, - TOGGLE_SIDEBAR (state) { - state.sidebar.open = !state.sidebar.open - } -} -export const getters = { - change (state) { - return state.change + mutations: { + CHANGE_LAYOUT (state) { + state.change++ + }, + CLOSE_SIDEBAR (state) { + state.sidebar.open = false + }, + OPEN_SIDEBAR (state) { + state.sidebar.open = true + }, + TOGGLE_SIDEBAR (state) { + state.sidebar.open = !state.sidebar.open + } }, - sidebar (state) { - return state.sidebar - } -} -export const actions = { - change ({commit}) { - commit('CHANGE_LAYOUT') - }, - closeSidebar ({commit}) { - commit('CLOSE_SIDEBAR') - }, - openSidebar ({commit}) { - commit('OPEN_SIDEBAR') + getters: { + change (state) { + return state.change + }, + sidebar (state) { + return state.sidebar + } }, - toggleSidebar ({commit}) { - commit('TOGGLE_SIDEBAR') + + actions: { + change ({commit}) { + commit('CHANGE_LAYOUT') + }, + closeSidebar ({commit}) { + commit('CLOSE_SIDEBAR') + }, + openSidebar ({commit}) { + commit('OPEN_SIDEBAR') + }, + toggleSidebar ({commit}) { + commit('TOGGLE_SIDEBAR') + } } } diff --git a/store/newsfeed.js b/store/newsfeed.js index 3b9fc4dc..1b08981f 100644 --- a/store/newsfeed.js +++ b/store/newsfeed.js @@ -2,8 +2,9 @@ import Vue from 'vue' import _ from 'lodash' import { Base64 } from 'js-base64' -export const state = () => { - return { +export default { + namespaced: true, + state: { contributions: [], search: null, filter: { @@ -22,194 +23,194 @@ export const state = () => { lastQueryDate: null, hasNext: false, lastScrollPos: null - } -} - -export const mutations = { - setLoading (state, isLoading) { - state.isLoading = isLoading - }, - movePaginationCursor (state) { - if (state.contributions.length) { - state.skip = state.skip + state.limit - } else { - state.skip = 0 - } - }, - setLastQueryHash (state, queryHash) { - if (state.lastQueryHash !== queryHash) { - state.lastQueryDate = new Date() - } - state.lastQueryHash = queryHash - }, - setSearch (state, value) { - state.search = value - }, - setFilter (state, value) { - state.filter = value - }, - addContributions (state, contributions) { - state.contributions = _.uniqBy(state.contributions.concat(contributions), '_id') }, - updateContribution (state, contribution) { - const index = _.findIndex(state.contributions, { _id: contribution._id }) - if (index >= 0) { - if (contribution.deleted) { - Vue.delete(state.contributions, index) + + mutations: { + setLoading (state, isLoading) { + state.isLoading = isLoading + }, + movePaginationCursor (state) { + if (state.contributions.length) { + state.skip = state.skip + state.limit } else { - Vue.set(state.contributions, index, contribution) + state.skip = 0 } - } - }, - setHasNext (state, hasNext) { - state.hasNext = hasNext - }, - setLastScrollPos (state, scrollPos) { - state.lastScrollPos = scrollPos - }, - setSortField (state, sortField) { - state.sortField = sortField - state.sort = {} - state.sort[state.sortField] = (state.sortOrder.toLowerCase() === 'desc') ? -1 : 1 - }, - setSortOrder (state, order) { - state.sortOrder = order - state.sort = {} - state.sort[state.sortField] = (state.sortOrder.toLowerCase() === 'desc') ? -1 : 1 - }, - clear (state) { - state.contributions = [] - state.skip = 0 - state.isLoading = false - state.hasNext = true - state.lastQueryHash = null - state.lastScrollPos = null - } -} - -export const getters = { - all (state) { - return state.contributions - }, - sortField (state) { - return state.sortField - }, - sortOrder (state) { - return state.sortOrder - }, - getCurrentQueryHash (state, getters, rootState, rootGetters) { - let queryData = { - search: state.search, - filter: state.filter, - limit: state.limit, - skip: state.skip, - sort: state.sort - } - if (!_.isEmpty(state.search)) { - queryData.$sort = {} - } - if (rootState.auth.user) { - queryData.language = { - $in: _.castArray(rootGetters['auth/userSettings'].contentLanguages) + }, + setLastQueryHash (state, queryHash) { + if (state.lastQueryHash !== queryHash) { + state.lastQueryDate = new Date() + } + state.lastQueryHash = queryHash + }, + setSearch (state, value) { + state.search = value + }, + setFilter (state, value) { + state.filter = value + }, + addContributions (state, contributions) { + state.contributions = _.uniqBy(state.contributions.concat(contributions), '_id') + }, + updateContribution (state, contribution) { + const index = _.findIndex(state.contributions, { _id: contribution._id }) + if (index >= 0) { + if (contribution.deleted) { + Vue.delete(state.contributions, index) + } else { + Vue.set(state.contributions, index, contribution) + } } + }, + setHasNext (state, hasNext) { + state.hasNext = hasNext + }, + setLastScrollPos (state, scrollPos) { + state.lastScrollPos = scrollPos + }, + setSortField (state, sortField) { + state.sortField = sortField + state.sort = {} + state.sort[state.sortField] = (state.sortOrder.toLowerCase() === 'desc') ? -1 : 1 + }, + setSortOrder (state, order) { + state.sortOrder = order + state.sort = {} + state.sort[state.sortField] = (state.sortOrder.toLowerCase() === 'desc') ? -1 : 1 + }, + clear (state) { + state.contributions = [] + state.skip = 0 + state.isLoading = false + state.hasNext = true + state.lastQueryHash = null + state.lastScrollPos = null } - const hash = JSON.stringify(queryData) - return Base64.encode(hash) - }, - isLoading (state) { - return state.isLoading - }, - hasNext (state) { - return state.hasNext }, - lastScrollPos (state) { - return state.lastScrollPos - }, - getCurrentQuery (state, getters, rootState, rootGetters) { - let query = { - $skip: state.skip, - $limit: state.limit, - $sort: state.sort, - visibility: 'public' - } - if (!_.isEmpty(state.search)) { - query.$sort = {} - } - query = Object.assign(query, rootGetters['search/queryLanguages']) - // generate the search query with the token entered inside the search field - if (!_.isEmpty(state.search)) { - // query.title = { $search: state.search } - query.$search = state.search - query.$language = Vue.i18n.locale() - } - // generate the category filter query by using the selected category ids - query = Object.assign(query, rootGetters['search/queryCategories']) + getters: { + all (state) { + return state.contributions + }, + sortField (state) { + return state.sortField + }, + sortOrder (state) { + return state.sortOrder + }, + getCurrentQueryHash (state, getters, rootState, rootGetters) { + let queryData = { + search: state.search, + filter: state.filter, + limit: state.limit, + skip: state.skip, + sort: state.sort + } + if (!_.isEmpty(state.search)) { + queryData.$sort = {} + } + if (rootState.auth.user) { + queryData.language = { + $in: _.castArray(rootGetters['auth/userSettings'].contentLanguages) + } + } + const hash = JSON.stringify(queryData) + return Base64.encode(hash) + }, + isLoading (state) { + return state.isLoading + }, + hasNext (state) { + return state.hasNext + }, + lastScrollPos (state) { + return state.lastScrollPos + }, + getCurrentQuery (state, getters, rootState, rootGetters) { + let query = { + $skip: state.skip, + $limit: state.limit, + $sort: state.sort, + visibility: 'public' + } + if (!_.isEmpty(state.search)) { + query.$sort = {} + } + query = Object.assign(query, rootGetters['search/queryLanguages']) - // generate the emotions filter query by using the selected emotions - query = Object.assign(query, rootGetters['search/queryEmotions']) + // generate the search query with the token entered inside the search field + if (!_.isEmpty(state.search)) { + // query.title = { $search: state.search } + query.$search = state.search + query.$language = Vue.i18n.locale() + } + // generate the category filter query by using the selected category ids + query = Object.assign(query, rootGetters['search/queryCategories']) - return query - } -} + // generate the emotions filter query by using the selected emotions + query = Object.assign(query, rootGetters['search/queryEmotions']) -export const actions = { - // Called from plugins/init-store-subscriptions only once - subscribe ({state, commit}) { - return this.app.$api.service('contributions') - .on('patched', (res) => { - commit('updateContribution', res) - }) - }, - /** - * fetch all contributions for the given query and filter settings - */ - async fetch ({state, getters, commit}) { - if (state.isLoading === true) { - // console.log('FETCH CANCELED AS THERE IS SOMETHING LOADING...') - return + return query } - commit('setLoading', true) + }, - // return current data if query is the same like the last one - const queryHash = getters.getCurrentQueryHash - if (!_.isEmpty(queryHash) && queryHash === state.lastQueryHash && state.contributions.length) { - // console.log('#LOAD FROM CACHE') - setTimeout(() => { - commit('setLoading', false) - }, 150) - return state.contributions - } + actions: { + // Called from plugins/init-store-subscriptions only once + subscribe ({state, commit}) { + return this.app.$api.service('contributions') + .on('patched', (res) => { + commit('updateContribution', res) + }) + }, + /** + * fetch all contributions for the given query and filter settings + */ + async fetch ({state, getters, commit}) { + if (state.isLoading === true) { + // console.log('FETCH CANCELED AS THERE IS SOMETHING LOADING...') + return + } + commit('setLoading', true) + + // return current data if query is the same like the last one + const queryHash = getters.getCurrentQueryHash + if (!_.isEmpty(queryHash) && queryHash === state.lastQueryHash && state.contributions.length) { + // console.log('#LOAD FROM CACHE') + setTimeout(() => { + commit('setLoading', false) + }, 150) + return state.contributions + } - const query = getters.getCurrentQuery + const query = getters.getCurrentQuery - commit('setLastQueryHash', queryHash) + commit('setLastQueryHash', queryHash) - try { - const res = await this.app.$api.service('contributions').find({query}) - commit('addContributions', res.data) + try { + const res = await this.app.$api.service('contributions').find({query}) + commit('addContributions', res.data) - setTimeout(() => { - const lastItemNum = res.data.length + res.skip - commit('setHasNext', lastItemNum < res.total) setTimeout(() => { - commit('setLoading', false) - }, 150) - }, 500) - } catch (err) { - commit('setLoading', false) - throw new Error(500, err.message) - } - }, - /** - * load more entries for the given query and filter settings - */ - fetchMore ({state, dispatch, commit}) { - if (state.isLoading === true) { - // console.log('FETCHMORE CANCELED AS THERE IS SOMETHING LOADING...') - return + const lastItemNum = res.data.length + res.skip + commit('setHasNext', lastItemNum < res.total) + setTimeout(() => { + commit('setLoading', false) + }, 150) + }, 500) + } catch (err) { + commit('setLoading', false) + throw new Error(500, err.message) + } + }, + /** + * load more entries for the given query and filter settings + */ + fetchMore ({state, dispatch, commit}) { + if (state.isLoading === true) { + // console.log('FETCHMORE CANCELED AS THERE IS SOMETHING LOADING...') + return + } + commit('movePaginationCursor') + dispatch('fetch') } - commit('movePaginationCursor') - dispatch('fetch') } } diff --git a/store/notifications.js b/store/notifications.js index bec5b94b..30dca5b8 100644 --- a/store/notifications.js +++ b/store/notifications.js @@ -4,237 +4,238 @@ const options = { limit: 10 } -export const state = () => { - return { +export default { + namespaced: true, + state: { total: 0, unseenTotal: 0, onlyUnseen: true, notifications: false, isLoading: false - } -} - -export const mutations = { - total (state, total) { - state.total = total - }, - unseenTotal (state, unseenTotal) { - state.unseenTotal = unseenTotal - }, - set (state, notifications) { - if (!notifications || notifications === undefined) { - state.notifications = null - } else { - state.notifications = notifications - } - }, - setOnlyUnseen (state, val) { - state.onlyUnseen = val - }, - clear (state) { - state.total = 0 - state.unseenTotal = 0 - state.notifications = [] - }, - add (state, notification) { - let toBottom = false - if (notification.toBottom) { - // to an object with more options, unwrap it... - toBottom = true - notification = notification.notification - } - const index = state.notifications.findIndex(item => { - return item._id === notification._id - }) - if (index > -1) { - // Replace existing notification - state.notifications.splice(index, 1, notification) - } else if (toBottom) { - // Or add new one - state.notifications.push(notification) - } else { - // Or add new one - state.notifications.unshift(notification) - } - }, - isLoading (state, isLoading) { - state.isLoading = isLoading - } -} - -export const getters = { - all (state) { - return state.notifications - }, - total (state) { - return state.total - }, - unseenTotal (state) { - return state.unseenTotal - }, - onlyUnseen (state) { - return state.onlyUnseen }, - hasMore (state) { - const total = state.onlyUnseen ? state.unseenTotal : state.total - return total && - total > state.notifications.length - }, - isLoading (state) { - return state.isLoading - } -} -export const actions = { - // Called from nuxtServerInit in index - init ({dispatch}) { - return dispatch('fetch') - }, - // Called from plugins/init-store-subscriptions only once - subscribe ({dispatch}) { - return this.app.$api.service('notifications') - .on('created', async (notification) => { - await dispatch('fetchOne', notification) - // Fetch total after notification is added - // Because it triggers infinite loader - dispatch('fetchTotal') - }) - .on('patched', (notification) => { - dispatch('fetchOne', notification) + mutations: { + total (state, total) { + state.total = total + }, + unseenTotal (state, unseenTotal) { + state.unseenTotal = unseenTotal + }, + set (state, notifications) { + if (!notifications || notifications === undefined) { + state.notifications = null + } else { + state.notifications = notifications + } + }, + setOnlyUnseen (state, val) { + state.onlyUnseen = val + }, + clear (state) { + state.total = 0 + state.unseenTotal = 0 + state.notifications = [] + }, + add (state, notification) { + let toBottom = false + if (notification.toBottom) { + // to an object with more options, unwrap it... + toBottom = true + notification = notification.notification + } + const index = state.notifications.findIndex(item => { + return item._id === notification._id }) - }, - find ({state, commit, rootGetters}, queryParams) { - let query = { - $limit: options.limit, - $sort: { - createdAt: -1 - }, - userId: rootGetters['auth/user']._id, - ...queryParams - } - if (state.onlyUnseen) { - query.unseen = true + if (index > -1) { + // Replace existing notification + state.notifications.splice(index, 1, notification) + } else if (toBottom) { + // Or add new one + state.notifications.push(notification) + } else { + // Or add new one + state.notifications.unshift(notification) + } + }, + isLoading (state, isLoading) { + state.isLoading = isLoading } - commit('isLoading', true) - return this.app.$api.service('notifications').find({ query }) }, - fetch ({commit, dispatch}) { - dispatch('fetchTotal') - return dispatch('find') - .then(result => { - commit('isLoading', false) - commit('set', result.data) - }) - .catch(error => { - commit('isLoading', false) - console.error('fetch could not fetch notifications', error) - }) - }, - fetchMore ({state, commit, dispatch}) { - const $skip = state.notifications.length - return dispatch('find', { $skip }) - .then(result => { - commit('isLoading', false) - dispatch('addMany', result.data) - }) - .catch(error => { - commit('isLoading', false) - console.error('fetchMore could not fetch notifications', error) - }) - }, - fetchOne ({commit, dispatch, rootGetters}, notification) { - // Only fetch notification for current user - if (notification.userId !== rootGetters['auth/user']._id) { - return + + getters: { + all (state) { + return state.notifications + }, + total (state) { + return state.total + }, + unseenTotal (state) { + return state.unseenTotal + }, + onlyUnseen (state) { + return state.onlyUnseen + }, + hasMore (state) { + const total = state.onlyUnseen ? state.unseenTotal : state.total + return total && + total > state.notifications.length + }, + isLoading (state) { + return state.isLoading } - commit('isLoading', true) - return this.app.$api.service('notifications').get(notification._id) - .then((result) => { - commit('isLoading', false) - commit('add', result) - }) - .catch(error => { - commit('isLoading', false) - console.error('fetchOne could not fetch notification:', notification._id) - console.error(error) - }) }, - fetchTotal ({commit, rootGetters}) { - let requests = [] - requests.push(this.app.$api.service('notifications').find({ - query: { - $limit: 0, + + actions: { + // Called from nuxtServerInit in index + init ({dispatch}) { + return dispatch('fetch') + }, + // Called from plugins/init-store-subscriptions only once + subscribe ({dispatch}) { + return this.app.$api.service('notifications') + .on('created', async (notification) => { + await dispatch('fetchOne', notification) + // Fetch total after notification is added + // Because it triggers infinite loader + dispatch('fetchTotal') + }) + .on('patched', (notification) => { + dispatch('fetchOne', notification) + }) + }, + find ({state, commit, rootGetters}, queryParams) { + let query = { + $limit: options.limit, + $sort: { + createdAt: -1 + }, userId: rootGetters['auth/user']._id, - unseen: true + ...queryParams } - }) - .then((result) => { - commit('unseenTotal', result.total) - }) - .catch(() => { - commit('clear') - })) - requests.push(this.app.$api.service('notifications').find({ - query: { - $limit: 0, - userId: rootGetters['auth/user']._id + if (state.onlyUnseen) { + query.unseen = true } - }) - .then((result) => { - commit('total', result.total) - }) - .catch(() => { - commit('clear') - })) - return Promise.all(requests) - }, - toggleUnseen ({state, commit, dispatch}) { - commit('setOnlyUnseen', !state.onlyUnseen) - dispatch('fetch') - }, - addMany ({state, commit}, notifications) { - notifications = _.castArray(notifications) - if (notifications || notifications.length) { - notifications.forEach(notification => { - commit('add', { notification, toBottom: true }) - }) - } - }, - async markAsRead ({dispatch}, {notification}) { - let result - - if (notification) { - // mark all as read with the same contribution id - let query = [{ - id: notification._id - }] - - if (!_.isEmpty(notification.relatedContributionId)) { - query.push({ - relatedContributionId: notification.relatedContributionId, - unseen: true + commit('isLoading', true) + return this.app.$api.service('notifications').find({ query }) + }, + fetch ({commit, dispatch}) { + dispatch('fetchTotal') + return dispatch('find') + .then(result => { + commit('isLoading', false) + commit('set', result.data) }) + .catch(error => { + commit('isLoading', false) + console.error('fetch could not fetch notifications', error) + }) + }, + fetchMore ({state, commit, dispatch}) { + const $skip = state.notifications.length + return dispatch('find', { $skip }) + .then(result => { + commit('isLoading', false) + dispatch('addMany', result.data) + }) + .catch(error => { + commit('isLoading', false) + console.error('fetchMore could not fetch notifications', error) + }) + }, + fetchOne ({commit, dispatch, rootGetters}, notification) { + // Only fetch notification for current user + if (notification.userId !== rootGetters['auth/user']._id) { + return } - - result = await this.app.$api.service('notifications').patch(null, { - unseen: false - }, { + commit('isLoading', true) + return this.app.$api.service('notifications').get(notification._id) + .then((result) => { + commit('isLoading', false) + commit('add', result) + }) + .catch(error => { + commit('isLoading', false) + console.error('fetchOne could not fetch notification:', notification._id) + console.error(error) + }) + }, + fetchTotal ({commit, rootGetters}) { + let requests = [] + requests.push(this.app.$api.service('notifications').find({ query: { - $or: query + $limit: 0, + userId: rootGetters['auth/user']._id, + unseen: true } }) - } else { - // mark all as read - result = await this.app.$api.service('notifications').patch(null, { - unseen: false - }, { + .then((result) => { + commit('unseenTotal', result.total) + }) + .catch(() => { + commit('clear') + })) + requests.push(this.app.$api.service('notifications').find({ query: { - unseen: true + $limit: 0, + userId: rootGetters['auth/user']._id } }) - } + .then((result) => { + commit('total', result.total) + }) + .catch(() => { + commit('clear') + })) + return Promise.all(requests) + }, + toggleUnseen ({state, commit, dispatch}) { + commit('setOnlyUnseen', !state.onlyUnseen) + dispatch('fetch') + }, + addMany ({state, commit}, notifications) { + notifications = _.castArray(notifications) + if (notifications || notifications.length) { + notifications.forEach(notification => { + commit('add', { notification, toBottom: true }) + }) + } + }, + async markAsRead ({dispatch}, {notification}) { + let result - dispatch('fetchTotal') - return result + if (notification) { + // mark all as read with the same contribution id + let query = [{ + id: notification._id + }] + + if (!_.isEmpty(notification.relatedContributionId)) { + query.push({ + relatedContributionId: notification.relatedContributionId, + unseen: true + }) + } + + result = await this.app.$api.service('notifications').patch(null, { + unseen: false + }, { + query: { + $or: query + } + }) + } else { + // mark all as read + result = await this.app.$api.service('notifications').patch(null, { + unseen: false + }, { + query: { + unseen: true + } + }) + } + + dispatch('fetchTotal') + return result + } } } diff --git a/store/organizations.js b/store/organizations.js index e0464f94..f9b752d2 100644 --- a/store/organizations.js +++ b/store/organizations.js @@ -1,18 +1,21 @@ -export const actions = { - async patch ({dispatch}, organization) { - if (!organization) { - return null +export default { + namespaced: true, + actions: { + async patch ({dispatch}, organization) { + if (!organization) { + return null + } + return this.app.$api.service('organizations').patch(organization._id, organization) + }, + create ({dispatch}, organization) { + return this.app.$api.service('organizations').create(organization) + }, + follow ({dispatch}, data) { + return this.app.$api.service('follows').create({ + followingId: data.organizationId, + type: 'organizations', + userId: data.currentUserId + }) } - return this.app.$api.service('organizations').patch(organization._id, organization) - }, - create ({dispatch}, organization) { - return this.app.$api.service('organizations').create(organization) - }, - follow ({dispatch}, data) { - return this.app.$api.service('follows').create({ - followingId: data.organizationId, - type: 'organizations', - userId: data.currentUserId - }) } } diff --git a/store/search.js b/store/search.js index 6de8c7e3..b78b4ca5 100644 --- a/store/search.js +++ b/store/search.js @@ -1,77 +1,77 @@ import { clone } from 'lodash' import searchQueryBuilder from './utils/search-query-builder' -export const state = () => { - return { +export default { + namespaced: true, + state: { query: '', filter: { categoryIds: [], emotions: [] } - } -} + }, -export const mutations = { - query (state, query) { - if (!query || query === undefined) { - state.query = null - } else { - state.query = query + mutations: { + query (state, query) { + if (!query || query === undefined) { + state.query = null + } else { + state.query = query - // go to newsfeed to see search results - if (this.app.router.currentRoute.name !== 'index') { - this.app.router.push({ name: 'index' }) + // go to newsfeed to see search results + if (this.app.router.currentRoute.name !== 'index') { + this.app.router.push({ name: 'index' }) + } + } + }, + categoryIds (state, categoryIds) { + if (!categoryIds || categoryIds === undefined) { + state.filter.categoryIds = [] + } else { + state.filter.categoryIds = clone(categoryIds) + } + }, + emotions (state, emotions) { + if (!emotions || emotions === undefined) { + state.filter.emotions = [] + } else { + state.filter.emotions = clone(emotions) } } }, - categoryIds (state, categoryIds) { - if (!categoryIds || categoryIds === undefined) { - state.filter.categoryIds = [] - } else { - state.filter.categoryIds = clone(categoryIds) - } - }, - emotions (state, emotions) { - if (!emotions || emotions === undefined) { - state.filter.emotions = [] - } else { - state.filter.emotions = clone(emotions) - } - } -} -export const getters = { - query (state) { - return state.query - }, - categoryIds (state) { - return clone(state.filter.categoryIds) - }, - emotions (state) { - return clone(state.filter.emotions) - }, - all (state) { - return state - }, - queryEmotions (state, getters, rootState, rootGetters) { - // generate the emotions filter query by using the selected emotions - return searchQueryBuilder.buildFilterEmotions(getters.emotions, {}) - }, - queryCategories (state, getters, rootState, rootGetters) { - // generate the category filter query by using the selected category ids - return searchQueryBuilder.buildFilterCategories(getters.categoryIds, {}) - }, - queryLanguages (state, getters, rootState, rootGetters) { - if (rootState.auth.user) { - const languages = rootGetters['auth/userSettings'].contentLanguages - return searchQueryBuilder.buildFilterLanguages(languages, {}) - } else { - return {} + getters: { + query (state) { + return state.query + }, + categoryIds (state) { + return clone(state.filter.categoryIds) + }, + emotions (state) { + return clone(state.filter.emotions) + }, + all (state) { + return state + }, + queryEmotions (state, getters, rootState, rootGetters) { + // generate the emotions filter query by using the selected emotions + return searchQueryBuilder.buildFilterEmotions(getters.emotions, {}) + }, + queryCategories (state, getters, rootState, rootGetters) { + // generate the category filter query by using the selected category ids + return searchQueryBuilder.buildFilterCategories(getters.categoryIds, {}) + }, + queryLanguages (state, getters, rootState, rootGetters) { + if (rootState.auth.user) { + const languages = rootGetters['auth/userSettings'].contentLanguages + return searchQueryBuilder.buildFilterLanguages(languages, {}) + } else { + return {} + } + }, + querySearch (state, getters, rootState, rootGetters) { + } }, - querySearch (state, getters, rootState, rootGetters) { - - } + actions: {} } - -export const actions = {} diff --git a/store/services/users.js b/store/services/users.js new file mode 100644 index 00000000..ae200954 --- /dev/null +++ b/store/services/users.js @@ -0,0 +1,11 @@ +import feathersVuex from 'feathers-vuex' + +let servicePlugin = (feathersClient) => { + const { service } = feathersVuex(feathersClient, { idField: '_id' }) + const servicePath = 'users' + const servicePlugin = service(servicePath, { + namespace: 'feathers-vuex-users' + }) + return servicePlugin +} +export default servicePlugin diff --git a/store/services/usersettings.js b/store/services/usersettings.js new file mode 100644 index 00000000..233538fc --- /dev/null +++ b/store/services/usersettings.js @@ -0,0 +1,11 @@ +import feathersVuex from 'feathers-vuex' + +let servicePlugin = (feathersClient) => { + const { service } = feathersVuex(feathersClient, { idField: '_id' }) + const servicePath = 'usersettings' + const servicePlugin = service(servicePath, { + namespace: 'feathers-vuex-usersettings' + }) + return servicePlugin +} +export default servicePlugin diff --git a/store/settings.js b/store/settings.js index 1d859e8f..1a0ef23e 100644 --- a/store/settings.js +++ b/store/settings.js @@ -1,7 +1,8 @@ import {isArray, intersection} from 'lodash' -export const state = () => { - return { +export default { + namespaced: true, + state: { settings: { _id: null, invites: { @@ -11,79 +12,78 @@ export const state = () => { }, maintenance: false } - } -} - -export const mutations = { - set (state, settings) { - state.settings = Object.assign(state.settings, settings) - } -} - -export const getters = { - get (state) { - return state.settings }, - showInvites (state, getters, rootState, rootGetters) { - if (!state.settings.invites.userCanInvite) { - return false + + mutations: { + set (state, settings) { + state.settings = Object.assign(state.settings, settings) } + }, - const user = rootGetters['auth/user'] - const badgeIds = user.badgeIds || [] - const inviteBadgeIds = state.settings.invites.onlyUserWithBadgesCanInvite + getters: { + get (state) { + return state.settings + }, + showInvites (state, getters, rootState, rootGetters) { + if (!state.settings.invites.userCanInvite) { + return false + } - if (user.role === 'admin') { - return true - } + const user = rootGetters['auth/user'] + const badgeIds = user.badgeIds || [] + const inviteBadgeIds = state.settings.invites.onlyUserWithBadgesCanInvite - if (inviteBadgeIds.length) { - return intersection(badgeIds, inviteBadgeIds).length - } - return state.settings.invites.userCanInvite - } -} + if (user.role === 'admin') { + return true + } -export const actions = { - // Called from nuxtServerInit in index - init ({dispatch}) { - return dispatch('fetch') - }, - // Called from plugins/init-store-subscriptions only once - subscribe ({state, commit}) { - return this.app.$api.service('settings') - .on('patched', (data) => { - if (state.settings._id === data._id) { - commit('set', data) - } - }) - }, - async fetch ({commit}) { - const service = this.app.$api.service('settings') - let res = await service.find({query: {key: 'system'}}) - if (isArray(res)) { - res = res.pop() + if (inviteBadgeIds.length) { + return intersection(badgeIds, inviteBadgeIds).length + } + return state.settings.invites.userCanInvite } + }, + actions: { + // Called from nuxtServerInit in index + init ({dispatch}) { + return dispatch('fetch') + }, + // Called from plugins/init-store-subscriptions only once + subscribe ({state, commit}) { + return this.app.$api.service('settings') + .on('patched', (data) => { + if (state.settings._id === data._id) { + commit('set', data) + } + }) + }, + async fetch ({commit}) { + const service = this.app.$api.service('settings') + let res = await service.find({query: {key: 'system'}}) + if (isArray(res)) { + res = res.pop() + } - await commit('set', res) + await commit('set', res) - return res - }, - async patch ({state, commit}, data) { - data = JSON.parse(JSON.stringify(data)) - const service = this.app.$api.service('settings') - let res - if (state.settings._id) { - res = await service.patch(state.settings._id, Object.assign(JSON.parse(JSON.stringify(state.settings)), data)) - } else { - res = await service.create(data) - } - if (isArray(res)) { - res = res.pop() - } + return res + }, + async patch ({state, commit}, data) { + data = JSON.parse(JSON.stringify(data)) + const service = this.app.$api.service('settings') + let res + if (state.settings._id) { + res = await service.patch(state.settings._id, Object.assign(JSON.parse(JSON.stringify(state.settings)), data)) + } else { + res = await service.create(data) + } + if (isArray(res)) { + res = res.pop() + } - await commit('set', res) + await commit('set', res) - return res + return res + } } } diff --git a/store/usersettings.js b/store/usersettings.js index 1744a5a9..62b635fb 100644 --- a/store/usersettings.js +++ b/store/usersettings.js @@ -1,22 +1,25 @@ import { isArray } from 'lodash' -export const actions = { - async patch ({dispatch, rootGetters}, data) { - const user = rootGetters['auth/user'] - const userSettings = rootGetters['auth/userSettings'] +export default { + namespaced: true, + actions: { + async patch ({dispatch, rootGetters}, data) { + const user = rootGetters['auth/user'] + const userSettings = rootGetters['auth/userSettings'] - if (!user || !userSettings) { - return null - } - data.userId = user._id.toString() + if (!user || !userSettings) { + return null + } + data.userId = user._id.toString() - let res = await this.app.$api.service('usersettings').create(data) - if (isArray(res)) { - res = res.pop() - } + let res = await this.app.$api.service('usersettings').create(data) + if (isArray(res)) { + res = res.pop() + } - await dispatch('auth/refreshUser', res, { root: true }) + await dispatch('auth/refreshUser', res, { root: true }) - return res + return res + } } } diff --git a/yarn.lock b/yarn.lock index d1523aef..ee71a04d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1780,7 +1780,7 @@ brorand@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" -browser-env@~3.2.4: +browser-env@^3.2.5: version "3.2.5" resolved "https://registry.yarnpkg.com/browser-env/-/browser-env-3.2.5.tgz#4345b8094413552e1e32c0c7b048b85d90965cc1" dependencies: @@ -2653,6 +2653,12 @@ cookie-universal@^2.0.3: dependencies: cookie "^0.3.1" +cookie-universal@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/cookie-universal/-/cookie-universal-2.0.5.tgz#42f109b96f9ff7e496e79914aaf77a4978ecab7e" + dependencies: + cookie "^0.3.1" + cookie@0.3.1, cookie@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" @@ -2990,14 +2996,6 @@ dasherize@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/dasherize/-/dasherize-2.0.0.tgz#6d809c9cd0cf7bb8952d80fc84fa13d47ddb1308" -data-urls@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-1.0.0.tgz#24802de4e81c298ea8a9388bb0d8e461c774684f" - dependencies: - abab "^1.0.4" - whatwg-mimetype "^2.0.0" - whatwg-url "^6.4.0" - date-now@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" @@ -3022,7 +3020,7 @@ debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6. dependencies: ms "2.0.0" -debug@^3.0.1, debug@^3.1.0, debug@~3.1.0: +debug@^3.0.0, debug@^3.0.1, debug@^3.1.0, debug@~3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" dependencies: @@ -4043,6 +4041,10 @@ falafel@^2.1.0: isarray "0.0.1" object-keys "^1.0.6" +fast-copy@^1.2.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/fast-copy/-/fast-copy-1.2.2.tgz#b15cf14d767f07c22cd5ffbd6cc615c12d206b7e" + fast-deep-equal@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" @@ -4091,6 +4093,39 @@ fbjs@^0.8.16: setimmediate "^1.0.5" ua-parser-js "^0.7.9" +feathers-commons@^0.8.0: + version "0.8.7" + resolved "https://registry.yarnpkg.com/feathers-commons/-/feathers-commons-0.8.7.tgz#11c6f25b537745a983e8d61552d7db8932d53782" + +feathers-errors@^2.9.2: + version "2.9.2" + resolved "https://registry.yarnpkg.com/feathers-errors/-/feathers-errors-2.9.2.tgz#96ca0e5fe50cc56f0eccc90ce3fa5e1f8840828d" + dependencies: + debug "^3.0.0" + +feathers-query-filters@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/feathers-query-filters/-/feathers-query-filters-2.1.2.tgz#cdb18224db5e19cc0140d528108e0908d5eb0654" + dependencies: + feathers-commons "^0.8.0" + +feathers-vuex@^1.4.8: + version "1.4.8" + resolved "https://registry.yarnpkg.com/feathers-vuex/-/feathers-vuex-1.4.8.tgz#c3ada49b72cdcf72d4e19b9f63435584a216136d" + dependencies: + "@feathersjs/commons" "^1.4.1" + debug "^3.1.0" + fast-copy "^1.2.0" + feathers-errors "^2.9.2" + feathers-query-filters "^2.1.2" + inflection "^1.12.0" + jwt-decode "^2.2.0" + lodash.isobject "^3.0.2" + lodash.merge "^4.6.0" + lodash.trim "^4.5.1" + serialize-error "^2.1.0" + sift "^5.0.0" + figures@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" @@ -5166,6 +5201,10 @@ indexof@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" +inflection@^1.12.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/inflection/-/inflection-1.12.0.tgz#a200935656d6f5f6bc4dc7502e1aecb703228416" + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -5823,37 +5862,6 @@ jsdom@11.6.2: ws "^4.0.0" xml-name-validator "^3.0.0" -jsdom@~11.10.0: - version "11.10.0" - resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.10.0.tgz#a42cd54e88895dc765f03f15b807a474962ac3b5" - dependencies: - abab "^1.0.4" - acorn "^5.3.0" - acorn-globals "^4.1.0" - array-equal "^1.0.0" - cssom ">= 0.3.2 < 0.4.0" - cssstyle ">= 0.2.37 < 0.3.0" - data-urls "^1.0.0" - domexception "^1.0.0" - escodegen "^1.9.0" - html-encoding-sniffer "^1.0.2" - left-pad "^1.2.0" - nwmatcher "^1.4.3" - parse5 "4.0.0" - pn "^1.1.0" - request "^2.83.0" - request-promise-native "^1.0.5" - sax "^1.2.4" - symbol-tree "^3.2.2" - tough-cookie "^2.3.3" - w3c-hr-time "^1.0.1" - webidl-conversions "^4.0.2" - whatwg-encoding "^1.0.3" - whatwg-mimetype "^2.1.0" - whatwg-url "^6.4.0" - ws "^4.0.0" - xml-name-validator "^3.0.0" - jsesc@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" @@ -5973,7 +5981,7 @@ jss@^9.8.1: symbol-observable "^1.1.0" warning "^3.0.0" -jwt-decode@^2.1.0: +jwt-decode@^2.1.0, jwt-decode@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-2.2.0.tgz#7d86bd56679f58ce6a84704a657dd392bba81a79" @@ -6225,6 +6233,10 @@ lodash.isequal@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" +lodash.isobject@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" + lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" @@ -6270,6 +6282,10 @@ lodash.templatesettings@^4.0.0: dependencies: lodash._reinterpolate "~3.0.0" +lodash.trim@^4.5.1: + version "4.5.1" + resolved "https://registry.yarnpkg.com/lodash.trim/-/lodash.trim-4.5.1.tgz#36425e7ee90be4aa5e27bcebb85b7d11ea47aa57" + lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" @@ -9422,6 +9438,10 @@ shuffle-seed@^1.1.6: dependencies: seedrandom "^2.4.2" +sift@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/sift/-/sift-5.1.0.tgz#1bbf2dfb0eb71e56c4cc7fb567fbd1351b65015e" + signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" @@ -11009,6 +11029,12 @@ vue-template-es2015-compiler@^1.5.0, vue-template-es2015-compiler@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/vue-template-es2015-compiler/-/vue-template-es2015-compiler-1.6.0.tgz#dc42697133302ce3017524356a6c61b7b69b4a18" +vue-test-utils@^1.0.0-beta.11: + version "1.0.0-beta.11" + resolved "https://registry.yarnpkg.com/vue-test-utils/-/vue-test-utils-1.0.0-beta.11.tgz#41b7fa53e0061d16ecbe18cfeea197c909d4ab8a" + dependencies: + lodash "^4.17.4" + vue-text-glitch@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/vue-text-glitch/-/vue-text-glitch-1.0.1.tgz#bc2d7bfd6413bed7597718d5b6ed3ae1a5374f7f" @@ -11264,10 +11290,6 @@ whatwg-fetch@>=0.10.0: version "2.0.4" resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz#dde6a5df315f9d39991aa17621853d720b85566f" -whatwg-mimetype@^2.0.0, whatwg-mimetype@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.1.0.tgz#f0f21d76cbba72362eb609dbed2a30cd17fcc7d4" - whatwg-url@^6.4.0: version "6.4.1" resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-6.4.1.tgz#fdb94b440fd4ad836202c16e9737d511f012fd67" From fe24fe3460b26b9be051d8afbf6e7b5e53e7f331 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=A4fer?= Date: Wed, 5 Sep 2018 15:00:23 +0200 Subject: [PATCH 14/14] Remove obsolete middleware --- middleware/authenticated.js | 26 -------------------------- nuxt.config.js | 12 +----------- pages/auth/name.vue | 1 - pages/auth/welcome.vue | 1 - pages/contributions/write.vue | 2 +- pages/organizations/_slug.vue | 1 - pages/organizations/create.vue | 1 - pages/profile/_slug.vue | 1 - 8 files changed, 2 insertions(+), 43 deletions(-) delete mode 100644 middleware/authenticated.js diff --git a/middleware/authenticated.js b/middleware/authenticated.js deleted file mode 100644 index a0e723eb..00000000 --- a/middleware/authenticated.js +++ /dev/null @@ -1,26 +0,0 @@ -import { isEmpty } from 'lodash' - -export default async ({ store, env, route, redirect }) => { - let publicPages = env.publicPages - publicPages.push('auth-logout') - // only affect non public pages - if (publicPages.indexOf(route.name) >= 0) { - return true - } - // await store.dispatch('auth/refreshJWT', 'authenticated middleware') - const isAuthenticated = await store.dispatch('auth/checkAuth') - if (isAuthenticated === true) { - return true - } - - // try to logout user - // await store.dispatch('auth/logout', null, { root: true }) - - // set the redirect path for after the login - let params = {} - if (!isEmpty(route.path) && route.path !== '/') { - params.path = route.path - } - - return redirect('/auth/login', params) -} diff --git a/nuxt.config.js b/nuxt.config.js index 8a491ebd..c26205b2 100644 --- a/nuxt.config.js +++ b/nuxt.config.js @@ -15,15 +15,6 @@ const envWhitelist = [ module.exports = { env: { // pages which do not require a login - publicPages: [ - 'auth-login', - 'auth-register', - 'auth-signup', - 'auth-reset', - 'auth-reset-token', - 'pages-slug', - 'test' - ], // pages to keep alive keepAlivePages: [ 'index' @@ -130,8 +121,7 @@ module.exports = { router: { middleware: [ 'maintenance', - 'check-auth', - 'authenticated' + 'auth' ], linkActiveClass: 'active-link' }, diff --git a/pages/auth/name.vue b/pages/auth/name.vue index 8169f211..3e7bd960 100644 --- a/pages/auth/name.vue +++ b/pages/auth/name.vue @@ -65,7 +65,6 @@ import RandomAvataaar from '~/components/Avatar/RandomAvataaar' export default { - middleware: 'authenticated', layout: 'blank', components: { RandomAvataaar diff --git a/pages/auth/welcome.vue b/pages/auth/welcome.vue index 9dfdcbf6..f9e06a8a 100644 --- a/pages/auth/welcome.vue +++ b/pages/auth/welcome.vue @@ -31,7 +31,6 @@ import {mapGetters} from 'vuex' export default { - middleware: 'authenticated', layout: 'blank', computed: { ...mapGetters({ diff --git a/pages/contributions/write.vue b/pages/contributions/write.vue index f241871f..9291ff06 100644 --- a/pages/contributions/write.vue +++ b/pages/contributions/write.vue @@ -15,7 +15,7 @@ import ContributionsForm from '~/components/Contributions/ContributionsForm.vue' export default { - middleware: ['authenticated', 'verified'], + middleware: ['verified'], mixins: [animatable], components: { ContributionsForm diff --git a/pages/organizations/_slug.vue b/pages/organizations/_slug.vue index 8ab31967..069b9d06 100644 --- a/pages/organizations/_slug.vue +++ b/pages/organizations/_slug.vue @@ -183,7 +183,6 @@ uploadingLogo: false } }, - middleware: ['authenticated'], async asyncData ({app, params, store, error}) { let organization if (!isEmpty(params) && !isEmpty(params.slug) && params.slug !== undefined) { diff --git a/pages/organizations/create.vue b/pages/organizations/create.vue index add88a9d..a9a65329 100644 --- a/pages/organizations/create.vue +++ b/pages/organizations/create.vue @@ -79,7 +79,6 @@ import OrgaFormStep2 from "~/components/Organizations/steps/OrgaFormStep2.vue"; import OrgaFormStep3 from "~/components/Organizations/steps/OrgaFormStep3.vue"; export default { - middleware: "authenticated", layout: "blank", mixins: [animatable], components: { diff --git a/pages/profile/_slug.vue b/pages/profile/_slug.vue index 8ac3e65c..302549f5 100644 --- a/pages/profile/_slug.vue +++ b/pages/profile/_slug.vue @@ -202,7 +202,6 @@ isLoadingCanDos: false } }, - middleware: ['authenticated'], async asyncData ({ app, params, store, error }) { let user let isOwner = false