diff --git a/.babelrc b/.babelrc deleted file mode 100644 index a11a507..0000000 --- a/.babelrc +++ /dev/null @@ -1,9 +0,0 @@ -{ - "presets": ["react", "es2015", "stage-0"], - "plugins": ["react-hot-loader/babel", "babel-plugin-transform-runtime"], - "env": { - "production": { - "presets": [["es2015", { "modules": false }], "react", "react-optimize", "stage-0"] - } - } -} diff --git a/.env.exemple b/.env.exemple new file mode 100644 index 0000000..18dd77a --- /dev/null +++ b/.env.exemple @@ -0,0 +1,11 @@ +APP_NAME=candilib +SECRET= +PORT=8000 +PORT_MAIL=3000 +HOST=http://localhost +SMTP_SERVER= +SMTP_PORT=25 +MAIL_FROM= +CANDIDAT_EXPIREDIN=72h +EMAIL_SUPPORT= +PUBLIC_URL=http://localhost:3000 diff --git a/.eslintrc b/.eslintrc deleted file mode 100644 index f23b951..0000000 --- a/.eslintrc +++ /dev/null @@ -1,37 +0,0 @@ -{ - "extends": ["react-app", "plugin:ava/recommended"], - "settings": { - "react": { - "pragma": "React", - "version": "16.6" - } - }, - "parser": "babel-eslint", - "plugins": ["ava"], - "env": { - "browser": true, - "node": true, - "mocha": true - }, - "rules": { - "no-console": 0, - "comma-dangle": [1, "always-multiline"], - "no-underscore-dangle": 0, - "max-len": [1, 180, 4], - "arrow-body-style": [0], - "react/jsx-no-bind": [0], - "import/no-unresolved": [0], - "import/no-extraneous-dependencies": [0], - "consistent-return": 0, - "array-callback-return": 0, - "no-param-reassign": [1, { "props": true }], - "react/prop-types": [1], - "react/prefer-stateless-function": 0, - "react/jsx-filename-extension": 0, - "react/jsx-indent": [0, 2], - "prefer-template": 0, - "func-names": 1, - "react/no-did-mount-set-state": 0, - "indent": [2, 2, { "ignoredNodes": ["TemplateLiteral > *"] }] - } -} diff --git a/.gitignore b/.gitignore index dc1359d..eb1768f 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,7 @@ server/config.js server/models/__tests__ .history .env -.env-prod +.env-prod env/* server/util/sendMail\.js\.cloud @@ -28,3 +28,4 @@ server/config\.home\.js candilib-build/ db/ data/ +client/build/ diff --git a/.travis.yml b/.travis.yml index 2c6e76c..f7c9c9e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -34,9 +34,9 @@ script: - echo "# build & run production" - make build-all-images - docker images && docker ps - - make up-prod + - make up-all-prod - make test-up - - make down-prod + - make down-all-prod after_script: - make clean-images clean-dir - echo "END" diff --git a/Dockerfile b/Dockerfile index 45a34e1..fe17c48 100644 --- a/Dockerfile +++ b/Dockerfile @@ -34,7 +34,7 @@ RUN if [ ! -z "$proxy" ] ; then \ [ -z "$npm_registry" ] || npm config set registry=$npm_registry ; \ npm install -COPY .env .eslintignore .eslintrc server/inbox/sites.json .babelrc index.js nodemon.json webpack.config.babel.js webpack.config.dev.js webpack.config.prod.js webpack.config.server.js ./ +COPY .env .eslintignore .eslintrc server/inbox/sites.json .babelrc index.js nodemon.json webpack.config.babel.js webpack.config.prod.js webpack.config.server.js ./ COPY client ./client COPY server ./server CMD ["npm", "start"] diff --git a/Dockerfile.back b/Dockerfile.back new file mode 100644 index 0000000..9e7b7d7 --- /dev/null +++ b/Dockerfile.back @@ -0,0 +1,56 @@ +FROM node:8-slim as base +ARG proxy +ARG npm_registry +# ARG sass_registry +ARG no_proxy + +ENV TZ=Europe/Paris +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +RUN mkdir -p /usr/src/app +WORKDIR /usr/src/app +EXPOSE 8000 + +RUN if [ ! -z "$proxy" ] ; then \ + npm config delete proxy; \ + npm config set proxy $proxy; \ + npm config set https-proxy $proxy ; \ + npm config set no-proxy $no_proxy; \ + fi ; \ + [ -z "$npm_registry" ] || npm config set registry=$npm_registry + +# +# target developement +# +FROM base as development +ENV NODE_ENV development + +COPY server/package.json server/package-lock.json ./ +RUN set -ex ; npm install + +COPY server/inbox/sites.json ./ +COPY server ./ + +CMD ["npm", "start"] + +# +# target build +# +FROM development as build +ENV NODE_ENV=production +RUN npm run build +#RUN set -ex ; npm run build 2>&1 | tee npm.log ; \ +# egrep -E '(Error|ERR|error)' npm.log && exit 1 ; rm -rf npm.log + +# +# target production (build front in prodution mode) +# +FROM base as production +ENV NODE_ENV=production +COPY server/package.json server/package-lock.json server/processes.json ./ +RUN set -ex ; npm install --production ; \ + npm install pm2 -g + +COPY server/index.js ./ +COPY --from=build /usr/src/app/dist ./dist +CMD ["pm2-runtime", "processes.json"] diff --git a/Dockerfile.front b/Dockerfile.front new file mode 100644 index 0000000..a14b8e7 --- /dev/null +++ b/Dockerfile.front @@ -0,0 +1,61 @@ +FROM node:8-slim as base +ARG proxy +ARG npm_registry +# ARG sass_registry +ARG no_proxy + +ENV TZ=Europe/Paris +RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone + +RUN mkdir -p /usr/src/app +WORKDIR /usr/src/app +EXPOSE 8000 + +RUN if [ ! -z "$proxy" ] ; then \ + npm config delete proxy; \ + npm config set proxy $proxy; \ + npm config set https-proxy $proxy ; \ + npm config set no-proxy $no_proxy; \ + fi ; \ + [ -z "$npm_registry" ] || npm config set registry=$npm_registry + +# +# target developement +# +FROM base as development +ENV NODE_ENV development + +COPY client/package.json client/package-lock.json ./ +RUN set -ex ; npm install + +COPY client ./ + +CMD ["npm", "start"] + +# +# target build +# +FROM development as build +ENV NODE_ENV=production +RUN npm run build +#RUN set -ex ; npm run build 2>&1 | tee npm.log ; \ +# egrep -E '(Error|ERR|error)' npm.log && exit 1 ; rm -rf npm.log + +# +# target production (build front in prodution mode) +# +FROM base as production +ENV NODE_ENV=production + +COPY --from=build /usr/src/app/build ./build + +# +# last default target (nginx+front build) +# +FROM nginx:latest +COPY --from=production /usr/src/app/build/ /usr/share/nginx/html +COPY nginx/run.sh /run.sh +COPY nginx/nginx-run.template /etc/nginx/conf.d/default.template +COPY nginx/nginx.template /etc/nginx/nginx.template +RUN [ -f "/run.sh" ] && chmod +x /run.sh +CMD ["/run.sh"] diff --git a/Makefile b/Makefile index 9969a95..b620885 100644 --- a/Makefile +++ b/Makefile @@ -76,13 +76,13 @@ stop-prod-web: ## Stop web container in production mode # build-all: build-dir build-archive build-all-images save-images ## Create archive, Build production images, save images -build-all-images: build-dir build-prod +build-all-images: build-dir build-all-prod build-archive: clean-archive build-dir ## Build archive @echo "Build $(APP) $(APP)-$(APP_VERSION) archive" echo "$(APP_VERSION)" > VERSION ; cp VERSION $(BUILD_DIR)/$(APP)-VERSION tar -zcvf $(BUILD_DIR)/$(FILE_ARCHIVE_APP_VERSION) --exclude $$(basename $(BUILD_DIR)) $$( git ls-files ) VERSION - # git archive --format=tar.gz -o $(BUILD_DIR)/$(FILE_ARCHIVE_APP_VERSION) HEAD +# # git archive --format=tar.gz -o $(BUILD_DIR)/$(FILE_ARCHIVE_APP_VERSION) HEAD @echo "Build $(APP) $(APP)-$(LATEST_VERSION) archive" cp $(BUILD_DIR)/$(FILE_ARCHIVE_APP_VERSION) $(BUILD_DIR)/$(FILE_ARCHIVE_LATEST_VERSION) @@ -90,6 +90,92 @@ clean-archive: @echo "Clean $(APP) archive" rm -rf $(FILE_ARCHIVE_APP_VERSION) +# +# refacto front/back +# +prepare-build-front: + if [ -f "${FRONTEND}/$(FILE_FRONTEND_APP_VERSION)" ] ; then rm -rf ${FRONTEND}/$(FILE_FRONTEND_APP_VERSION) ; fi + ( cd client && tar -zcvf $(FILE_FRONTEND_APP_VERSION) --exclude *.tar.gz --exclude Dockerfile.* . ) + +check-build-front-dev: ## Check front docker-compose syntax + ${DC} -f $(DC_APP_BUILD_FRONT_DEV) config -q + +build-front-dev: check-build-front ## Build front container + ${DC} -f ${DC_APP_BUILD_FRONT_DEV} build ${DC_BUILD_ARGS} +run-front-dev: ## Run front container + ${DC} -f ${DC_APP_BUILD_FRONT_DEV} up -d +down-front-dev: ## Down front container + ${DC} -f ${DC_APP_BUILD_FRONT_DEV} down + +check-build-front-prod: ## Check front docker-compose syntax + ${DC} -f $(DC_APP_BUILD_FRONT_PROD) config +build-front-prod: check-build-front-prod ## Build front container + sed -i.back -e 's/ "version":.*/ "version": "${APP_VERSION}",/' client/package.json + proxy="${proxy}" NPM_REGISTRY="${NPM_REGISTRY}" no_proxy="${no_proxy}" ${DC} -f ${DC_APP_BUILD_FRONT_PROD} build ${DC_BUILD_ARGS} +run-front-prod: ## Run front container + ${DC} -f ${DC_APP_BUILD_FRONT_PROD} up -d +stop-front-prod: ## Down front container + ${DC} -f ${DC_APP_BUILD_FRONT_PROD} stop web +down-front-prod: ## Down front container + ${DC} -f ${DC_APP_BUILD_FRONT_PROD} down + +check-build-back-prod: ## Check back docker-compose syntax + ${DC} -f $(DC_APP_BUILD_BACK_PROD) config +build-back-prod: check-build-back-prod ## Build back container + sed -i.back -e 's/ "version":.*/ "version": "${APP_VERSION}",/' server/package.json + proxy="${proxy}" NPM_REGISTRY="${NPM_REGISTRY}" no_proxy="${no_proxy}" ${DC} -f ${DC_APP_BUILD_BACK_PROD} build ${DC_BUILD_ARGS} +run-back-prod: ## Build back container + ${DC} -f ${DC_APP_BUILD_BACK_PROD} up -d +down-back-prod: ## Build back container + ${DC} -f ${DC_APP_BUILD_BACK_PROD} down +stop-back-prod: ## Down back container + ${DC} -f ${DC_APP_BUILD_BACK_PROD} stop back + +check-build-db-prod: ## Check db docker-compose syntax + ${DC} -f $(DC_APP_BUILD_BACK_PROD) config +build-db-prod: check-build-db-prod ## Build db container + ${DC} -f ${DC_APP_BUILD_BACK_PROD} pull db + proxy="${proxy}" NPM_REGISTRY="${NPM_REGISTRY}" no_proxy="${no_proxy}" ${DC} -f ${DC_APP_BUILD_BACK_PROD} build ${DC_BUILD_ARGS} db +run-db-prod: ## Build db container + ${DC} -f ${DC_APP_BUILD_BACK_PROD} up -d +down-db-prod: ## Build db container + ${DC} -f ${DC_APP_BUILD_BACK_PROD} down +stop-db-prod: ## Down db container + ${DC} -f ${DC_APP_BUILD_BACK_PROD} stop db + +# +## All container web + back + db +# +up-all-prod: check-run-all-prod up-all-prod-db wait-db ## Run all containers (front+back+db)in production mode + ${DC} -f ${DC_APP_RUN_ALL_PROD} up -d --no-build + +check-run-all-prod: check-prerequisites ## Check production compose syntax + ${DC} -f $(DC_APP_RUN_ALL_PROD) config -q + +up-all-prod-web: ## Up web container in production mode + ${DC} -f ${DC_APP_RUN_ALL_PROD} up -d --no-build web +up-all-prod-back: ## Up back container in production mode + ${DC} -f ${DC_APP_RUN_ALL_PROD} up -d --no-build back +up-all-prod-db: ## Up db container in production mode + ${DC} -f ${DC_APP_RUN_ALL_PROD} up -d --no-build db + +down-all-prod: check-run-all-prod ## Stop containers in production mode + ${DC} -f ${DC_APP_RUN_ALL_PROD} down + +stop-all-prod-web: ## Stop web container in production mode + ${DC} -f ${DC_APP_RUN_ALL_PROD} stop web +stop-all-prod-back: ## Stop back container in production mode + ${DC} -f ${DC_APP_RUN_ALL_PROD} stop back +stop-all-prod-db: ## Stop db container in production mode + ${DC} -f ${DC_APP_RUN_ALL_PROD} stop db + +build-all-prod: check-prerequisites check-build-all-prod build-front-prod build-back-prod build-db-prod ## Build the release and production (web && && back && db) + +check-build-all-prod: check-build-back-prod check-build-front-prod check-build-db-prod + +# +# old build prod +# build-prod: check-prerequisites check-build-prod build-prod-web build-prod-db ## Build the release and production (web && db) check-build-prod: ## Check production docker-compose syntax @@ -103,30 +189,42 @@ build-prod-db: ## Build production db container ${DC} -f ${DC_APP_BUILD_PROD} build ${DC_BUILD_ARGS} db # -# save/clean images +# save images # -save-images: build-dir save-image-db save-image-web ## Save images +save-images: build-dir save-image-db save-image-web save-image-back ## Save images + +save-image-web: ## Save web image + web_image_name=$$(${DC} -f $(DC_APP_BUILD_FRONT_PROD) config | python -c 'import sys, yaml, json; cfg = json.loads(json.dumps(yaml.load(sys.stdin), sys.stdout, indent=4)); print cfg["services"]["web"]["image"]') ; \ + docker image save -o $(BUILD_DIR)/$(FILE_IMAGE_WEB_APP_VERSION) $$web_image_name && \ + cp $(BUILD_DIR)/$(FILE_IMAGE_WEB_APP_VERSION) $(BUILD_DIR)/$(FILE_IMAGE_WEB_LATEST_VERSION) save-image-db: ## Save db image - db_image_name=$$(${DC} -f $(DC_APP_BUILD_PROD) config | python -c 'import sys, yaml, json; cfg = json.loads(json.dumps(yaml.load(sys.stdin), sys.stdout, indent=4)); print cfg["services"]["db"]["image"]') ; \ + db_image_name=$$(${DC} -f $(DC_APP_BUILD_BACK_PROD) config | python -c 'import sys, yaml, json; cfg = json.loads(json.dumps(yaml.load(sys.stdin), sys.stdout, indent=4)); print cfg["services"]["db"]["image"]') ; \ docker image save -o $(BUILD_DIR)/$(FILE_IMAGE_DB_APP_VERSION) $$db_image_name && \ cp $(BUILD_DIR)/$(FILE_IMAGE_DB_APP_VERSION) $(BUILD_DIR)/$(FILE_IMAGE_DB_LATEST_VERSION) -save-image-web: ## Save web image - web_image_name=$$(${DC} -f $(DC_APP_BUILD_PROD) config | python -c 'import sys, yaml, json; cfg = json.loads(json.dumps(yaml.load(sys.stdin), sys.stdout, indent=4)); print cfg["services"]["web"]["image"]') ; \ - docker image save -o $(BUILD_DIR)/$(FILE_IMAGE_WEB_APP_VERSION) $$web_image_name && \ - cp $(BUILD_DIR)/$(FILE_IMAGE_WEB_APP_VERSION) $(BUILD_DIR)/$(FILE_IMAGE_WEB_LATEST_VERSION) +save-image-back: ## Save back image + back_image_name=$$(${DC} -f $(DC_APP_BUILD_BACK_PROD) config | python -c 'import sys, yaml, json; cfg = json.loads(json.dumps(yaml.load(sys.stdin), sys.stdout, indent=4)); print cfg["services"]["back"]["image"]') ; \ + docker image save -o $(BUILD_DIR)/$(FILE_IMAGE_BACK_APP_VERSION) $$back_image_name && \ + cp $(BUILD_DIR)/$(FILE_IMAGE_BACK_APP_VERSION) $(BUILD_DIR)/$(FILE_IMAGE_BACK_LATEST_VERSION) -clean-images: clean-image-web clean-image-db ## Remove all docker images +# +# clean image +# +clean-images: clean-image-web clean-image-back clean-image-db ## Remove all docker images clean-image-web: ## Remove web docker image - web_image_name=$$(${DC} -f $(DC_APP_BUILD_PROD) config | python -c 'import sys, yaml, json; cfg = json.loads(json.dumps(yaml.load(sys.stdin), sys.stdout, indent=4)); print cfg["services"]["web"]["image"]') ; \ + web_image_name=$$(${DC} -f $(DC_APP_BUILD_FRONT_PROD) config | python -c 'import sys, yaml, json; cfg = json.loads(json.dumps(yaml.load(sys.stdin), sys.stdout, indent=4)); print cfg["services"]["web"]["image"]') ; \ docker rmi $$web_image_name || true clean-image-db: ## Remove db docker image - db_image_name=$$(${DC} -f $(DC_APP_BUILD_PROD) config | python -c 'import sys, yaml, json; cfg = json.loads(json.dumps(yaml.load(sys.stdin), sys.stdout, indent=4)); print cfg["services"]["db"]["image"]') ; \ + db_image_name=$$(${DC} -f $(DC_APP_BUILD_BACK_PROD) config | python -c 'import sys, yaml, json; cfg = json.loads(json.dumps(yaml.load(sys.stdin), sys.stdout, indent=4)); print cfg["services"]["db"]["image"]') ; \ docker rmi $$db_image_name || true +clean-image-back: ## Remove back docker image + back_image_name=$$(${DC} -f $(DC_APP_BUILD_BACK_PROD) config | python -c 'import sys, yaml, json; cfg = json.loads(json.dumps(yaml.load(sys.stdin), sys.stdout, indent=4)); print cfg["services"]["back"]["image"]') ; \ + docker rmi $$back_image_name || true + build-dir: if [ ! -d "$(BUILD_DIR)" ] ; then mkdir -p $(BUILD_DIR) ; fi @@ -147,6 +245,7 @@ publish-$(APP_VERSION): $(APP)-VERSION \ $(FILE_ARCHIVE_APP_VERSION) \ $(FILE_IMAGE_WEB_APP_VERSION) \ + $(FILE_IMAGE_BACK_APP_VERSION) \ $(FILE_IMAGE_DB_APP_VERSION) \ ; do \ curl -k -X PUT -T $$file -H 'X-Auth-Token: $(PUBLISH_AUTH_TOKEN)' $(PUBLISH_URL)/$(PUBLISH_URL_APP_VERSION)/$$file ; \ @@ -161,24 +260,27 @@ publish-$(LATEST_VERSION): for file in \ $(APP)-VERSION \ $(FILE_ARCHIVE_LATEST_VERSION) \ - $(FILE_IMAGE_WEB_APP_VERSION) \ - $(FILE_IMAGE_DB_APP_VERSION) \ + $(FILE_IMAGE_WEB_LATEST_VERSION) \ + $(FILE_IMAGE_BACK_LATEST_VERSION) \ + $(FILE_IMAGE_DB_LATEST_VERSION) \ ; do \ curl -k -X PUT -T $$file -H 'X-Auth-Token: $(PUBLISH_AUTH_TOKEN)' $(PUBLISH_URL)/$(PUBLISH_URL_LATEST_VERSION)/$$file ; \ done ; \ - curl -k -H 'X-Auth-Token: $(PUBLISH_AUTH_TOKEN)' "$(PUBLISH_URL)/$(PUBLISH_URL_BASE)?prefix=${APP_VERSION}/&format=json" -s --fail ; \ + curl -k -H 'X-Auth-Token: $(PUBLISH_AUTH_TOKEN)' "$(PUBLISH_URL)/$(PUBLISH_URL_BASE)?prefix=${LATEST_VERSION}/&format=json" -s --fail ; \ ) # # test # -test-up: wait-db test-up-db test-up-web test-up-$(APP) ## Test running container (db,web,app) +test-up: wait-db test-up-db test-up-back test-up-web test-up-$(APP) ## Test running container (db,web,app) wait-db: ## wait db up and running time bash -x tests/wait-db.sh -test-up-web: ## test web container up and runnng +test-up-web: ## test web container up and running time bash -x tests/test-up-web.sh -test-up-db: ## test db container up and runnng +test-up-back: ## test back container up and running + time bash -x tests/test-up-back.sh +test-up-db: ## test db container up and running time bash -x tests/test-up-db.sh test-up-${APP}: ## test app up and running - time bash tests/test-up-${APP}.sh + time bash -x tests/test-up-${APP}.sh diff --git a/Makefile.inc b/Makefile.inc index 82962c0..11bef98 100644 --- a/Makefile.inc +++ b/Makefile.inc @@ -11,26 +11,43 @@ BUILD_DIR=${APP_PATH}/${APP}-build # binaries DOCKER := $(shell type -p docker) DC := $(shell type -p docker-compose) +proxy := $(shell echo $$http_proxy) +no_proxy := $(shell echo $$no_proxy) # cli docker-compose DC_BUILD_ARGS = --pull --no-cache --force-rm # docker-compose file: development or production (build or run time) -DC_APP_BUILD_DEV := docker-compose.yml -DC_APP_RUN_DEV := docker-compose.yml -DC_APP_BUILD_PROD := docker-compose-production.yml -DC_APP_RUN_PROD := docker-compose-run-production.yml +#DC_APP_BUILD_DEV := docker-compose.yml +#DC_APP_RUN_DEV := docker-compose.yml +DC_APP_BUILD_DEV := dev.docker-compose.yml +DC_APP_RUN_DEV := dev.docker-compose.yml +DC_APP_BUILD_PROD := docker-compose-production.yml +DC_APP_RUN_PROD := docker-compose-run-production.yml +# +DC_APP_BUILD_FRONT_PROD := docker-compose-build-front.yml +DC_APP_RUN_FRONT_PROD := docker-compose-build-front.yml +DC_APP_BUILD_FRONT_DEV := docker-compose-build-front-dev.yml +DC_APP_RUN_FRONT_DEV := docker-compose-build-front-dev.yml +DC_APP_BUILD_BACK_PROD := docker-compose-build-back.yml +DC_APP_RUN_BACK_PROD := docker-compose-build-back.yml +# +DC_APP_RUN_ALL_PROD := docker-compose-run-all-prod.yml # detect tty -USE_TTY := $(shell test -t 1 && { DOCKER_USE_TTY="-t" ; DOCKER_COMPOSER_USE_TTY="-T" ; } ) +DOCKER_USE_TTY := $(shell test -t 1 || echo "-t" ) +DOCKER_COMPOSE_USE_TTY := $(shell test -t 1 || echo "-T" ) # source archive FILE_ARCHIVE_APP_VERSION = $(APP)-$(APP_VERSION)-archive.tar.gz FILE_ARCHIVE_LATEST_VERSION = $(APP)-$(LATEST_VERSION)-archive.tar.gz +FILE_FRONT_APP_VERSION = $(APP)-$(APP_VERSION)-front.tar.gz # docker image name FILE_IMAGE_WEB_APP_VERSION = $(APP)-web-$(APP_VERSION)-image.tar FILE_IMAGE_WEB_LATEST_VERSION = $(APP)-web-$(LATEST_VERSION)-image.tar +FILE_IMAGE_BACK_APP_VERSION = $(APP)-back-$(APP_VERSION)-image.tar +FILE_IMAGE_BACK_LATEST_VERSION = $(APP)-back-$(LATEST_VERSION)-image.tar FILE_IMAGE_DB_APP_VERSION = $(APP)-db-$(APP_VERSION)-image.tar FILE_IMAGE_DB_LATEST_VERSION = $(APP)-db-$(LATEST_VERSION)-image.tar @@ -50,7 +67,7 @@ NPM_REGISTRY = $(shell echo $$NPM_REGISTRY ) # Run env # Mongo DB volume path (in production mode) -DBDATA := ${APP_PATH}/data +DBDATA := ${APP_PATH}/db # export all variables in subshell export diff --git a/README.md b/README.md index 6b20614..e69de29 100644 --- a/README.md +++ b/README.md @@ -1,250 +0,0 @@ -# Candilib application pour l'épreuve pratique du permis de conduire en candidat libre -## Quickstart - -```sh - npm install -g mern-cli - mern init your_new_app - cd your_new_app - npm install - npm start -``` - -**Note : Please make sure your MongoDB is running.** For MongoDB installation guide see [this](https://docs.mongodb.org/v3.0/installation/). Also `npm6` is required to install dependencies properly. - -## Available Commands - -1. `npm run start` - starts the development server with hot reloading enabled - -2. `npm run bs` - bundles the code and starts the production server - -3. `npm run test` - start the test runner - -4. `npm run watch:test` - start the test runner with watch mode - -5. `npm run cover` - generates test coverage report - -6. `npm run lint` - runs linter to check for lint errors - -## File Structure - -### Webpack Configs - -MERN uses Webpack for bundling modules. There are four types of Webpack configs provided `webpack.config.dev.js` (for development), `webpack.config.prod.js` (for production), `webpack.config.server.js` (for bundling server in production) and `webpack.config.babel.js` (for [babel-plugin-webpack-loaders](https://github.com/istarkov/babel-plugin-webpack-loaders) for server rendering of assets included through webpack). - -The Webpack configuration is minimal and beginner-friendly. You can customise and add more features to it for production build. - -### Server - -MERN uses express web framework. Our app sits in server.js where we check for NODE_ENV. - -If NODE_ENV is development, we apply Webpack middlewares for bundling and Hot Module Replacement. - -#### Server Side Rendering - -We use React Router's match function for handling all page requests so that browser history works. - -All the routes are defined in `client/routes.js`. React Router renders components according to route requested. - -```js -// Server Side Rendering based on routes matched by React-router. -app.use((req, res) => { - match({ - routes, - location: req.url - }, (err, redirectLocation, renderProps) => { - if (err) { - return res.status(500).end('Internal server error'); - } - - if (!renderProps) { - return res.status(404).end('Not found!'); - } - - const initialState = { - posts: [], - post: {} - }; - - const store = configureStore(initialState); - - fetchComponentData(store.dispatch, renderProps.components, renderProps.params).then(() => { - const initialView = renderToString( - - - - ); - - const finalState = store.getState(); - - res.status(200).end(renderFullPage(initialView, finalState)); - }).catch(() => { - res.end(renderFullPage('Error', {})); - }); - }); -}); -``` - -`match` takes two parameters, first is an object that contains routes, location and history and second is a callback function which is called when routes have been matched to a location. - -If there's an error in matching we return 500 status code, if no matches are found we return 404 status code. If a match is found then, we need to create a new Redux Store instance. - -**Note:** A new Redux Store has populated afresh on every request. - -`fetchComponentData` is the essential function. It takes three params: first is a dispatch function of Redux store, the second is an array of components that should be rendered in current route and third is the route params. `fetchComponentData` collects all the needs (need is an array of actions that are required to be dispatched before rendering the component) of components in the current route. It returns a promise when all the required actions are dispatched. We render the page and send data to the client for client-side rendering in `window.__INITIAL_STATE__`. - -### Client - -Client directory contains all the shared components, routes, modules. - -#### components -This folder contains all the common components which are used throughout the project. - -#### index.js -Index.js simply does client side rendering using the data provided from `window.__INITIAL_STATE__`. - -#### modules -Modules are the way of organising different domain-specific modules in the project. A typical module contains the following -``` -. -└── Post - ├── __tests__ // all the tests for this module goes here - | ├── components // Sub components of this module - | | ├── Post.spec.js - | | ├── PostList.spec.js - | | ├── PostItem.spec.js - | | └── PostImage.spec.js - | ├── pages - | | ├── PostPage.spec.js - | | └── PostViewPage.spec.js - | ├── PostReducer.spec.js - | └── PostActions.spec.js - ├── components // Sub components of this module - | ├── Post.js - | ├── PostList.js - | ├── PostItem.js - | └── PostImage.js - ├── pages // React Router Pages from this module - | ├── PostPage - | | ├── PostPage.js - | | └── PostPage.css - | └── PostViewPage - | ├── PostViewPage.js - | └── PostViewPage.css - ├── PostReducer.js - └── PostActions.js -``` - -## Misc - -### Importing Assets -Assets can be kept where you want and can be imported into your js files or css files. Those fill be served by webpack in development mode and copied to the dist folder during production. - -### ES6 support -We use babel to transpile code in both server and client with `stage-0` plugin. So, you can use both ES6 and experimental ES7 features. - -### Docker -There are docker configurations for both development and production. - -To run docker for development: -```sh -docker-compose build # re-run after changing dependencies -docker-compose up -``` -or, if you want to override the web port: -```sh -WEB_PORT= docker-compose up -``` - -To run docker for production: -```sh -docker-compose -f docker-compose-production.yml up --build -``` - -To reset the database: -```sh -docker-compose down --volumes -``` - -### Make your MERN -In this version, we enabled the `mern-cli` to clone not only this project but also the variants of `mern-starter` like one project with MaterialUI or JWT auth. To make your version of MERN, follow these steps - -1. Clone this project - ```sh - git clone https://github.com/Hashnode/mern-starter - ``` - -2. Make your changes. Add a package, add authentication, modify the file structure, replace Redux with MobX or anything else. - -3. In this version, we also added code generators. Blueprints for those generators are located at `config/blueprints`, and config is located at `mern.json`. Make sure to edit them if necessary after your made modifications in the previous step. There is a section below which explains how to modify generators. - -4. Next clone `mern-cli` project - ```sh - git clone https://github.com/Hashnode/mern-cli - ``` - -5. Add your project details to `variants.json` in the cloned project and send a pull request. - -### Modifying Generators - -#### mern.json -It contains a blueprints array. Each object in it is the config for a generator. A blueprint config contains the name, description, usage, and files array. An example blueprint config -```json -{ - "name": "dumb-s", - "description": "Generates a dumb react component in shared components", - "usage": "dumb-s [component-name]", - "files": [ - { - "blueprint-path": "config/blueprints/dumb-component.ejs", - "target-path": "client/components/<%= helpers.capitalize(name) %>.js" - } - ] -} -``` - -A file object contains - -1. `blueprint-path` - location of the blueprint file - -2. `target-path` - location where the file should be generated - -3. `parent-path` - optional parameter, used if you want to generate the file inside an already existing folder in your project. - -Also, `target-path` supports [ejs](https://github.com/mde/ejs) and the following variables will be passed while rendering, - -1. `name` - `` input from user - -2. `parent` - in particular special cases where you need to generate files inside an already existing folder, you can obtain this parent variable from the user. A config using that will look like, - ```json - { - "name": "dumb-m", - "description": "Generates a dumb react component in a module directory", - "usage": "dumb-m /", - "files": [ - { - "blueprint-path": "config/blueprints/dumb-component.ejs", - "parent-path": "client/modules/<%= helpers.capitalize(parent) %>", - "target-path": "components/<%= helpers.capitalize(name) %>/<%= helpers.capitalize(name) %>.js" - } - ] - } - ``` - Here, notice the usage. In `/`, `` will be passed as `parent` and `` will be passed as ``. - -3. `helpers` - an helper object is passed which include common utility functions. For now, it contains `capitalize`. If you want to add more, send a PR to [mern-cli](https://github.com/Hashnode/mern-cli). - -#### Blueprint files -Blueprints are basically [ejs](https://github.com/mde/ejs) templates which are rendered with the same three variables (`name`, optional `parent` and `helpers` object) as above. - -### Caveats - -#### FOUC (Flash of Unstyled Content) -To make the hot reloading of CSS work, we are not extracting CSS in development. Ideally, during server rendering, we will be extracting CSS, and we will get a .css file, and we can use it in the html template. That's what we are doing in production. - -In development, after all scripts get loaded, react loads the CSS as BLOBs. That's why there is a second of FOUC in development. - -#### Client and Server Markup Mismatch -This warning is visible only on development and totally harmless. This occurs to hash difference in `react-router`. To solve it, react router docs asks you to use `match` function. If we use `match`, `react-hot-reloader` stops working. - -## License -MERN is released under the [MIT License](http://www.opensource.org/licenses/MIT). diff --git a/client/App.js b/client/App.js deleted file mode 100644 index 9274f13..0000000 --- a/client/App.js +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Root Component - */ -import React from 'react'; -import PropTypes from 'prop-types'; -import { Provider } from 'react-redux'; -import { Router, browserHistory } from 'react-router'; - -// Import Routes -import routes from './routes'; - -// Base stylesheet -require('./main.css'); - -export default function MainApp({ store }) { - return ( - - - {routes} - - - ); -} - -MainApp.propTypes = { - store: PropTypes.object.isRequired, // eslint-disable-line react/forbid-prop-types -}; diff --git a/client/config/env.js b/client/config/env.js new file mode 100644 index 0000000..b0344c5 --- /dev/null +++ b/client/config/env.js @@ -0,0 +1,93 @@ +'use strict'; + +const fs = require('fs'); +const path = require('path'); +const paths = require('./paths'); + +// Make sure that including paths.js after env.js will read .env variables. +delete require.cache[require.resolve('./paths')]; + +const NODE_ENV = process.env.NODE_ENV; +if (!NODE_ENV) { + throw new Error( + 'The NODE_ENV environment variable is required but was not specified.' + ); +} + +// https://github.com/bkeepers/dotenv#what-other-env-files-can-i-use +var dotenvFiles = [ + `${paths.dotenv}.${NODE_ENV}.local`, + `${paths.dotenv}.${NODE_ENV}`, + // Don't include `.env.local` for `test` environment + // since normally you expect tests to produce the same + // results for everyone + NODE_ENV !== 'test' && `${paths.dotenv}.local`, + paths.dotenv, +].filter(Boolean); + +// Load environment variables from .env* files. Suppress warnings using silent +// if this file is missing. dotenv will never modify any environment variables +// that have already been set. Variable expansion is supported in .env files. +// https://github.com/motdotla/dotenv +// https://github.com/motdotla/dotenv-expand +dotenvFiles.forEach(dotenvFile => { + if (fs.existsSync(dotenvFile)) { + require('dotenv-expand')( + require('dotenv').config({ + path: dotenvFile, + }) + ); + } +}); + +// We support resolving modules according to `NODE_PATH`. +// This lets you use absolute paths in imports inside large monorepos: +// https://github.com/facebook/create-react-app/issues/253. +// It works similar to `NODE_PATH` in Node itself: +// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders +// Note that unlike in Node, only *relative* paths from `NODE_PATH` are honored. +// Otherwise, we risk importing Node.js core modules into an app instead of Webpack shims. +// https://github.com/facebook/create-react-app/issues/1023#issuecomment-265344421 +// We also resolve them to make sure all tools using them work consistently. +const appDirectory = fs.realpathSync(process.cwd()); +process.env.NODE_PATH = (process.env.NODE_PATH || '') + .split(path.delimiter) + .filter(folder => folder && !path.isAbsolute(folder)) + .map(folder => path.resolve(appDirectory, folder)) + .join(path.delimiter); + +// Grab NODE_ENV and REACT_APP_* environment variables and prepare them to be +// injected into the application via DefinePlugin in Webpack configuration. +const REACT_APP = /^REACT_APP_/i; + +function getClientEnvironment(publicUrl) { + const raw = Object.keys(process.env) + .filter(key => REACT_APP.test(key)) + .reduce( + (env, key) => { + env[key] = process.env[key]; + return env; + }, + { + // Useful for determining whether we’re running in production mode. + // Most importantly, it switches React into the correct mode. + NODE_ENV: process.env.NODE_ENV || 'development', + // Useful for resolving the correct path to static assets in `public`. + // For example, . + // This should only be used as an escape hatch. Normally you would put + // images into the `src` and `import` them in code to get their paths. + PUBLIC_URL: publicUrl, + } + ); + // Stringify all values so we can feed into Webpack DefinePlugin + const stringified = { + 'process.env': Object.keys(raw).reduce((env, key) => { + env[key] = JSON.stringify(raw[key]); + return env; + }, {}), + }; + + return { raw, stringified }; +} + +module.exports = getClientEnvironment; diff --git a/client/config/jest/cssTransform.js b/client/config/jest/cssTransform.js new file mode 100644 index 0000000..8f65114 --- /dev/null +++ b/client/config/jest/cssTransform.js @@ -0,0 +1,14 @@ +'use strict'; + +// This is a custom Jest transformer turning style imports into empty objects. +// http://facebook.github.io/jest/docs/en/webpack.html + +module.exports = { + process() { + return 'module.exports = {};'; + }, + getCacheKey() { + // The output is always the same. + return 'cssTransform'; + }, +}; diff --git a/client/config/jest/fileTransform.js b/client/config/jest/fileTransform.js new file mode 100644 index 0000000..07010e3 --- /dev/null +++ b/client/config/jest/fileTransform.js @@ -0,0 +1,30 @@ +'use strict'; + +const path = require('path'); + +// This is a custom Jest transformer turning file imports into filenames. +// http://facebook.github.io/jest/docs/en/webpack.html + +module.exports = { + process(src, filename) { + const assetFilename = JSON.stringify(path.basename(filename)); + + if (filename.match(/\.svg$/)) { + return `module.exports = { + __esModule: true, + default: ${assetFilename}, + ReactComponent: (props) => ({ + $$typeof: Symbol.for('react.element'), + type: 'svg', + ref: null, + key: null, + props: Object.assign({}, props, { + children: ${assetFilename} + }) + }), + };`; + } + + return `module.exports = ${assetFilename};`; + }, +}; diff --git a/client/config/paths.js b/client/config/paths.js new file mode 100644 index 0000000..c24b4dd --- /dev/null +++ b/client/config/paths.js @@ -0,0 +1,89 @@ +'use strict'; + +const path = require('path'); +const fs = require('fs'); +const url = require('url'); + +// Make sure any symlinks in the project folder are resolved: +// https://github.com/facebook/create-react-app/issues/637 +const appDirectory = fs.realpathSync(process.cwd()); +const resolveApp = relativePath => path.resolve(appDirectory, relativePath); + +const envPublicUrl = process.env.PUBLIC_URL; + +function ensureSlash(inputPath, needsSlash) { + const hasSlash = inputPath.endsWith('/'); + if (hasSlash && !needsSlash) { + return inputPath.substr(0, inputPath.length - 1); + } else if (!hasSlash && needsSlash) { + return `${inputPath}/`; + } else { + return inputPath; + } +} + +const getPublicUrl = appPackageJson => + envPublicUrl || require(appPackageJson).homepage; + +// We use `PUBLIC_URL` environment variable or "homepage" field to infer +// "public path" at which the app is served. +// Webpack needs to know it to put the right - - - - - `; -}; - -const renderError = (err) => { - const softTab = ' '; - const errTrace = isProdMode - ? `:

${softTab}${err.stack.replace(
-        /\n/g,
-        `
${softTab}`, - )}
` - : ''; - return renderFullPage(`Server Error${errTrace}`, {}); -}; - -// Server Side Rendering based on routes matched by React-router. -app.use((req, res, next) => { - match({ routes, location: req.url }, (err, redirectLocation, renderProps) => { - if (err) { - return res.status(500).end(renderError(err)); - } - - if (redirectLocation) { - return res.redirect( - 302, - redirectLocation.pathname + redirectLocation.search, - ); - } - - if (!renderProps) { - return next(); - } - - // PrivateRoute - if (typeof routes.props.children === 'object') { - const childrenPrivateRoute = routes.props.children.find((child) => { - return ( - child.type !== undefined - && child.props !== undefined - && renderProps !== undefined - && renderProps.location !== undefined - && child.type.name === 'PrivateRoute' - && child.props.path === renderProps.location.pathname.toLowerCase() - && renderProps.location.pathname !== '/auth' - ); - }); - if (childrenPrivateRoute !== undefined) { - return res.redirect( - 302, - '/auth?redirect=' + renderProps.location.pathname, - ); - } - } - - const store = configureStore(); - - return fetchComponentData(store, renderProps.components, renderProps.params) - .then(() => { - const initialView = renderToString( - - - , - ); - const finalState = store.getState(); - - res - .set('Content-Type', 'text/html') - .status(200) - .end(renderFullPage(initialView, finalState)); - }) - .catch(error => next(error)); - }); -}); - -// start app -app.listen(serverConfig.port, (error) => { - if (!error) { - console.log( - `Candilib is running on port: ${process.env.PORT - || serverConfig.port}! Build something amazing!`, - ); // eslint-disable-line - } -}); - -export default app; +export default app diff --git a/server/util/constant.js b/server/util/constant.js index e703a47..9bee412 100644 --- a/server/util/constant.js +++ b/server/util/constant.js @@ -1,11 +1,11 @@ -export const CANDIDAT_EXISTANT = 'OK'; -export const CANDIDAT_NOK = 'NOK'; -export const CANDIDAT_NOK_NOM = 'NOK Nom'; -export const EPREUVE_PRATIQUE_OK = 'OK'; -export const EPREUVE_ETG_KO = 'KO'; -export const INSCRIPTION_OK = 'SIGNUP_OK'; -export const INSCRIPTION_VALID = 'VALID'; -export const AURIGE_OK = 'CHECK_OK'; -export const MAIL_CONVOCATION = 'MAIL_CONVOCATION'; -export const ANNULATION_CONVOCATION = 'ANNULATION_CONVOCATION'; -export const INSCRIPTION_UPDATE = 'SIGNUP_UPDATE'; +export const CANDIDAT_EXISTANT = 'OK' +export const CANDIDAT_NOK = 'NOK' +export const CANDIDAT_NOK_NOM = 'NOK Nom' +export const EPREUVE_PRATIQUE_OK = 'OK' +export const EPREUVE_ETG_KO = 'KO' +export const INSCRIPTION_OK = 'SIGNUP_OK' +export const INSCRIPTION_VALID = 'VALID' +export const AURIGE_OK = 'CHECK_OK' +export const MAIL_CONVOCATION = 'MAIL_CONVOCATION' +export const ANNULATION_CONVOCATION = 'ANNULATION_CONVOCATION' +export const INSCRIPTION_UPDATE = 'SIGNUP_UPDATE' diff --git a/server/util/crypto.js b/server/util/crypto.js index 743661b..3bca6d3 100644 --- a/server/util/crypto.js +++ b/server/util/crypto.js @@ -1,13 +1,9 @@ -import bcrypt from 'bcryptjs'; +import bcrypt from 'bcryptjs' -const iterations = 8192; -const keyLength = 32; -const digest = 'sha512'; - -export function getHash(password) { - return bcrypt.hashSync(password, 8); +export function getHash (password) { + return bcrypt.hashSync(password, 8) } -export function compareToHash(original, password) { +export function compareToHash (original, password) { return bcrypt.compareSync(original, password) } diff --git a/server/util/csv-express-candilib.js b/server/util/csv-express-candilib.js index 34ef677..1a6d516 100644 --- a/server/util/csv-express-candilib.js +++ b/server/util/csv-express-candilib.js @@ -6,13 +6,13 @@ MIT Licensed */ -const res = require('http').ServerResponse.prototype; -const iconv = require('iconv-lite'); +const res = require('http').ServerResponse.prototype +const iconv = require('iconv-lite') // Configurable settings -exports.separator = ';'; -exports.preventCast = false; -exports.ignoreNullOrUndefined = true; +exports.separator = ';' +exports.preventCast = false +exports.ignoreNullOrUndefined = true /* * Escape CSV field @@ -22,14 +22,14 @@ exports.ignoreNullOrUndefined = true; * @api private */ -function escape(field) { +function escape (field) { if (exports.ignoreNullOrUndefined && field === undefined) { - return ''; + return '' } if (exports.preventCast) { - return `= ${String(field).replace(/"/g, '""')}`; + return `= ${String(field).replace(/"/g, '""')}` } - return String(field).replace(/"/g, '""'); + return String(field).replace(/"/g, '""') } /* @@ -41,74 +41,74 @@ function escape(field) { {status} - Optional status code */ -export default (res.csv = function sendCsv( +export default (res.csv = function sendCsv ( data, charset, csvHeaders, headers, status, ) { - let body = ''; - const headerRow = []; - let statusCodeSet = true; + let body = '' + const headerRow = [] + let statusCodeSet = true - this.charset = charset || 'utf-8'; - this.header('Content-Type', 'text/csv'); + this.charset = charset || 'utf-8' + this.header('Content-Type', 'text/csv') // Set custom headers if (headers && headers instanceof Object) { // Use res.header() instead of res.set() to maintain backward compatibility with Express 2 // Change to res.set() in next major version so that iteration is not required Object.keys(headers).forEach((header) => { - this.header(header, headers[header]); - }); + this.header(header, headers[header]) + }) } // Set response status code if (status && Number.isInteger(status)) { // res.status does not work in Express 2, so make sure the error would be trapped try { - this.status(status); + this.status(status) } catch (error) { - statusCodeSet = false; + statusCodeSet = false } } // headerRow is used to ensure key order Object.keys(data[0]).map((prop) => { if (data[0].hasOwnProperty(prop)) { // eslint-disable-line no-prototype-builtins - headerRow.push(prop); + headerRow.push(prop) } - }); + }) // Append the header row to the response if requested if (csvHeaders) { - body += `${headerRow.join(exports.separator)}\r\n`; // TODO remettre avec retour chariot sous linux \r\n + body += `${headerRow.join(exports.separator)}\r\n` // TODO remettre avec retour chariot sous linux \r\n } // Convert the data to a CSV-like structure // eslint-disable-next-line no-restricted-syntax for (const line of data) { - let convertedLine = line; + let convertedLine = line if (!(line instanceof Array)) { convertedLine = headerRow.map((key) => { if (line.hasOwnProperty(key)) { // eslint-disable-line no-prototype-builtins - return line[key]; + return line[key] } - return null; - }); + return null + }) } - body += `${convertedLine.map(escape).join(exports.separator)}\r\n`; // TODO remettre avec retour chariot sous linux \r\n + body += `${convertedLine.map(escape).join(exports.separator)}\r\n` // TODO remettre avec retour chariot sous linux \r\n } if (this.charset !== 'utf-8') { - body = iconv.encode(body, this.charset); + body = iconv.encode(body, this.charset) } if (!statusCodeSet) { - return this.send(body, status); + return this.send(body, status) } - return this.send(body); -}); + return this.send(body) +}) diff --git a/server/util/fetchData.js b/server/util/fetchData.js index 37f1d52..a61f6bd 100644 --- a/server/util/fetchData.js +++ b/server/util/fetchData.js @@ -2,14 +2,14 @@ Utility function to fetch required data for component to render in server side. This was inspired from https://github.com/caljrimmer/isomorphic-redux-app/blob/73e6e7d43ccd41e2eb557a70be79cebc494ee54b/src/common/api/fetchComponentDataBeforeRender.js */ -import sequence from './promiseUtils'; +import sequence from './promiseUtils' -export default function fetchComponentData(store, components, params) { +export default function fetchComponentData (store, components, params) { const needs = components.reduce((prev, current) => { return (current.need || []) .concat((current.WrappedComponent && (current.WrappedComponent.need !== current.need) ? current.WrappedComponent.need : []) || []) - .concat(prev); - }, []); + .concat(prev) + }, []) - return sequence(needs, need => store.dispatch(need(params, store.getState()))); + return sequence(needs, need => store.dispatch(need(params, store.getState()))) } diff --git a/server/util/isAdmin.js b/server/util/isAdmin.js index f5ac1d5..589a993 100644 --- a/server/util/isAdmin.js +++ b/server/util/isAdmin.js @@ -1,8 +1,12 @@ -export default function isAdmin(req, res, next) { +export default function isAdmin (req, res, next) { if (req.userLevel === 1) { - return next(); + return next() } res .status(401) - .send({ auth: false, message: 'Erreur de vérification Token.' }); + .send({ + auth: false, + message: 'Erreur de vérification Token.', + success: false, + }) } diff --git a/server/util/jwt.constant.js b/server/util/jwt.constant.js index cbb3e47..eb243ca 100644 --- a/server/util/jwt.constant.js +++ b/server/util/jwt.constant.js @@ -1,27 +1,27 @@ -import moment from 'moment'; -import serverConfig from '../config'; +import moment from 'moment' +import serverConfig from '../config' export const USER_STATUS_EXPIRES_IN = { admin: () => { - const now = moment(); + const now = moment() const midnight = now .clone() .hour(23) .minute(59) .second(59) - .millisecond(0); + .millisecond(0) if (midnight.isBefore(now)) { - midnight.add(1, 'days'); + midnight.add(1, 'days') } - const duration = midnight.diff(now) / 1000; + const duration = midnight.diff(now) / 1000 - return duration + 's'; + return duration + 's' }, candidat: () => { - return serverConfig.tokenCandidatExpired; + return serverConfig.tokenCandidatExpired }, -}; +} export const USER_STATUS_LEVEL = { admin: 1, -}; +} diff --git a/server/util/messageMailManager.js b/server/util/messageMailManager.js index b84ff2a..98199af 100644 --- a/server/util/messageMailManager.js +++ b/server/util/messageMailManager.js @@ -1,4 +1,4 @@ -import moment from 'moment'; +import moment from 'moment' import { CANDIDAT_NOK, CANDIDAT_NOK_NOM, @@ -10,32 +10,31 @@ import { AURIGE_OK, MAIL_CONVOCATION, ANNULATION_CONVOCATION, -} from './constant'; -import sites from '../inbox/sites.json'; -import serverConfig from '../config'; +} from './constant' +import sites from '../inbox/sites.json' +import serverConfig from '../config' const mailMessage = (candidatAurige, flag, urlMagicLink) => { - const urlFAQ = `${serverConfig.PUBLIC_URL}/informations`; - const urlRESA = `${serverConfig.PUBLIC_URL}/auth?redirect=calendar`; - const urlConnexion = `${serverConfig.PUBLIC_URL}`; + const urlFAQ = `${serverConfig.PUBLIC_URL}/informations` + const urlRESA = `${serverConfig.PUBLIC_URL}/auth?redirect=calendar` + const urlConnexion = `${serverConfig.PUBLIC_URL}` - const { codeNeph, nomNaissance, creneau } = candidatAurige; + const { codeNeph, nomNaissance, creneau } = candidatAurige - const message = {}; + const message = {} - const nomMaj = nomNaissance ? nomNaissance.toUpperCase() : ''; + const nomMaj = nomNaissance ? nomNaissance.toUpperCase() : '' - const site = creneau && creneau.title ? creneau.title : ''; + const site = creneau && creneau.title ? creneau.title : '' const dateCreneau = creneau && creneau.start ? moment(creneau.start).format('DD MMMM YYYY') - : ''; + : '' const heureCreneau = creneau && creneau.start - ? moment(creneau.start).format('HH:mm') : ''; - - let siteAdresse = []; + ? moment(creneau.start).format('HH:mm') : '' + let siteAdresse = [] if (creneau && creneau.title) { - siteAdresse = sites.find(item => item.nom.toUpperCase() === creneau.title); + siteAdresse = sites.find(item => item.nom.trim().toUpperCase() === creneau.title.trim()) } const ANNULATION_CONVOCATION_MSG = `

Bonjour Mr/Mme ${nomMaj},

@@ -43,7 +42,7 @@ const mailMessage = (candidatAurige, flag, urlMagicLink) => {

votre réservation à l'examen pratique du permis de conduire avec le numéro NEPH ${codeNeph} est bien annulée.


-

L'équipe Candilib

`; +

L'équipe Candilib

` const MAIL_CONVOCATION_MSG = `

Le présent mail vaut convocation.

@@ -76,14 +75,9 @@ const mailMessage = (candidatAurige, flag, urlMagicLink) => {
  • - Vous présenterez un titre d’identité en cours de validité : carte nationale - d’identité, passeport ou titre de séjour - (liste complète {' '} - - arrêté du 23 décembre 2016 relatif à la justification de - l'identité, du domicile, de la résidence normale et de la - régularité du séjour pour l'obtention du permis de conduire - ). + Vous présenterez un titre d’identité en cours de validité, + carte nationale d’identité, passeport, titre de séjour, etc. : + liste complète ici.

  • @@ -130,7 +124,7 @@ const mailMessage = (candidatAurige, flag, urlMagicLink) => { Pour toute information, vous pouvez consulter notre aide en ligne.


    -

    L'équipe Candilib

    `; +

    L'équipe Candilib

    ` const INSCRIPTION_OK_MSG = `

    Bonjour Mr/Mme ${nomMaj},


    @@ -158,7 +152,7 @@ const mailMessage = (candidatAurige, flag, urlMagicLink) => { Attention:vous ne devez transmettre cet email à personne. Il permet d'accéder à votre compte personnel, de créer ou modifier votre réservation.

    -

    L'équipe Candilib

    `; +

    L'équipe Candilib

    ` const INSCRIPTION_KO_MSG = `

    Bonjour Mr/Mme ${nomMaj},


    @@ -171,7 +165,7 @@ const mailMessage = (candidatAurige, flag, urlMagicLink) => {

    Veuillez consulter notre aide en ligne.


    -

    L'équipe Candilib

    `; +

    L'équipe Candilib

    ` const EPREUVE_PRATIQUE_OK_MSG = `

    Bonjour Mr/Mme ${nomMaj},


    @@ -185,7 +179,7 @@ const mailMessage = (candidatAurige, flag, urlMagicLink) => {

    Vous pourrez trouver des informations utiles en consultant notre aide en ligne.


    -

    L'équipe Candilib

    `; +

    L'équipe Candilib

    ` const EPREUVE_ETG_KO_MSG = `

    Bonjour Mr/Mme ${nomMaj},


    @@ -193,58 +187,58 @@ const mailMessage = (candidatAurige, flag, urlMagicLink) => {

    Vous ne pouvez pas rejoindre le site de réservation des candidats libres sans examen du code de la route réussi et en cours de validité.

    Vous pourrez trouver des informations utiles en consultant notre aide en ligne.


    -

    L'équipe Candilib

    `; +

    L'équipe Candilib

    ` const INSCRIPTION_VALID_MSG = `

    Bonjour Mr/Mme ${nomMaj},

    Votre demande d’inscription est en cours de vérification, vous recevrez une information sous 48h hors week-end et jours fériés.


    -

    L'équipe Candilib

    `; +

    L'équipe Candilib

    ` switch (flag) { - case CANDIDAT_NOK: - message.content = INSCRIPTION_KO_MSG; - message.subject = 'Inscription Candilib non validée'; - return message; - case INSCRIPTION_VALID: - message.content = INSCRIPTION_VALID_MSG; - message.subject = "Confirmation d'inscription Candilib"; - return message; - case CANDIDAT_NOK_NOM: - message.content = INSCRIPTION_KO_MSG; - message.subject = 'Inscription Candilib non validée'; - return message; - case EPREUVE_PRATIQUE_OK: - message.content = EPREUVE_PRATIQUE_OK_MSG; - message.subject = 'Problème inscription Candilib'; - return message; - case INSCRIPTION_OK: - message.content = INSCRIPTION_VALID_MSG; - message.subject = 'Inscription Candilib en attente de vérification'; - return message; - case EPREUVE_ETG_KO: - message.content = EPREUVE_ETG_KO_MSG; - message.subject = 'Problème inscription Candilib'; - return message; - case AURIGE_OK: - message.content = INSCRIPTION_OK_MSG; - message.subject = 'Validation de votre inscription à Candilib'; - return message; - case MAIL_CONVOCATION: - message.content = MAIL_CONVOCATION_MSG; - message.subject = "Convocation à l'examen"; - return message; - case ANNULATION_CONVOCATION: - message.content = ANNULATION_CONVOCATION_MSG; - message.subject = "Annulation de Convocation à l'examen"; - return message; - case INSCRIPTION_UPDATE: - message.content = INSCRIPTION_VALID_MSG; - message.subject = 'Inscription Candilib en attente de vérification'; - return message; - default: - return ''; + case CANDIDAT_NOK: + message.content = INSCRIPTION_KO_MSG + message.subject = 'Inscription Candilib non validée' + return message + case INSCRIPTION_VALID: + message.content = INSCRIPTION_VALID_MSG + message.subject = "Confirmation d'inscription Candilib" + return message + case CANDIDAT_NOK_NOM: + message.content = INSCRIPTION_KO_MSG + message.subject = 'Inscription Candilib non validée' + return message + case EPREUVE_PRATIQUE_OK: + message.content = EPREUVE_PRATIQUE_OK_MSG + message.subject = 'Problème inscription Candilib' + return message + case INSCRIPTION_OK: + message.content = INSCRIPTION_VALID_MSG + message.subject = 'Inscription Candilib en attente de vérification' + return message + case EPREUVE_ETG_KO: + message.content = EPREUVE_ETG_KO_MSG + message.subject = 'Problème inscription Candilib' + return message + case AURIGE_OK: + message.content = INSCRIPTION_OK_MSG + message.subject = 'Validation de votre inscription à Candilib' + return message + case MAIL_CONVOCATION: + message.content = MAIL_CONVOCATION_MSG + message.subject = "Convocation à l'examen" + return message + case ANNULATION_CONVOCATION: + message.content = ANNULATION_CONVOCATION_MSG + message.subject = "Annulation de Convocation à l'examen" + return message + case INSCRIPTION_UPDATE: + message.content = INSCRIPTION_VALID_MSG + message.subject = 'Inscription Candilib en attente de vérification' + return message + default: + return '' } -}; +} -export default mailMessage; +export default mailMessage diff --git a/server/util/promiseUtils.js b/server/util/promiseUtils.js index cec54ff..185ade5 100644 --- a/server/util/promiseUtils.js +++ b/server/util/promiseUtils.js @@ -2,20 +2,20 @@ * Throw an array to it and a function which can generate promises * and it will call them sequentially, one after another */ -export default function sequence(items, consumer) { - const results = []; +export default function sequence (items, consumer) { + const results = [] const runner = () => { - const item = items.shift(); + const item = items.shift() if (item) { return consumer(item) .then((result) => { - results.push(result); + results.push(result) }) - .then(runner); + .then(runner) } - return Promise.resolve(results); - }; + return Promise.resolve(results) + } - return runner(); + return runner() } diff --git a/server/util/redirect2Level.js b/server/util/redirect2Level.js index 2153216..9f014b6 100644 --- a/server/util/redirect2Level.js +++ b/server/util/redirect2Level.js @@ -1 +1 @@ -export const REDIRECTTOLEVEL = {}; // eslint-disable-line import/prefer-default-export +export const REDIRECTTOLEVEL = {} // eslint-disable-line import/prefer-default-export diff --git a/server/util/sendMagicLink.js b/server/util/sendMagicLink.js index ec63e4e..26f5c9c 100644 --- a/server/util/sendMagicLink.js +++ b/server/util/sendMagicLink.js @@ -1,22 +1,20 @@ -import nodemailer from 'nodemailer'; -import smtpTransport from 'nodemailer-smtp-transport'; -import mailMessage from './messageMailManager'; -import serverConfig, { smtpOptions } from '../config'; - +import nodemailer from 'nodemailer' +import smtpTransport from 'nodemailer-smtp-transport' +import mailMessage from './messageMailManager' +import serverConfig, { smtpOptions } from '../config' const sendMagicLink = (candidatAurige, token) => { - const flag = 'CHECK_OK'; - const url = `${serverConfig.PUBLIC_URL}${serverConfig.authentificationRoute}?token=${encodeURIComponent(token)}&redirect=calendar`; + const flag = 'CHECK_OK' + const url = `${serverConfig.PUBLIC_URL}${serverConfig.authentificationRoute}?token=${encodeURIComponent(token)}&redirect=calendar` - const message = mailMessage(candidatAurige, flag, url); + const message = mailMessage(candidatAurige, flag, url) // const INSCRIPTION_OK_MSG = `Bienvenue sur Candilib!
    \n\r // Vous êtes inscrit sur le site de réservation des candidats libres.
    \n\r // Voici votre identifiant: ${email}\n`; const transporter = nodemailer.createTransport( smtpTransport(smtpOptions), - ); - + ) const mailOptions = { from: serverConfig.mailFrom, @@ -160,20 +158,20 @@ const sendMagicLink = (candidatAurige, token) => { `, - }; + } return new Promise((resolve, reject) => { transporter.sendMail(mailOptions, (err, info) => { if (err) { - transporter.close(); - reject(err); // eslint-disable-line no-console + transporter.close() + reject(err) // eslint-disable-line no-console } else { - console.log('MagicLink sent: ' + info.response); - transporter.close(); - resolve(info.response); + console.log('MagicLink sent: ' + info.response) + transporter.close() + resolve(info.response) } - }); - }); -}; + }) + }) +} -export default sendMagicLink; +export default sendMagicLink diff --git a/server/util/sendMail.js b/server/util/sendMail.js index df05d50..0a29679 100644 --- a/server/util/sendMail.js +++ b/server/util/sendMail.js @@ -1,13 +1,12 @@ -import nodemailer from 'nodemailer'; -import smtpTransport from 'nodemailer-smtp-transport'; -import mailMessage from './messageMailManager'; -import serverConfig, { smtpOptions } from '../config'; - +import nodemailer from 'nodemailer' +import smtpTransport from 'nodemailer-smtp-transport' +import mailMessage from './messageMailManager' +import serverConfig, { smtpOptions } from '../config' const sendMailToAccount = (candidatAurige, flag) => { - const message = mailMessage(candidatAurige, flag); + const message = mailMessage(candidatAurige, flag) - const transporter = nodemailer.createTransport(smtpTransport(smtpOptions)); + const transporter = nodemailer.createTransport(smtpTransport(smtpOptions)) const mailOptions = { from: serverConfig.mailFrom, @@ -152,17 +151,17 @@ const sendMailToAccount = (candidatAurige, flag) => { `, - }; + } transporter.sendMail(mailOptions, (err, info) => { if (err) { - console.log(err); // eslint-disable-line no-console + console.log(err) // eslint-disable-line no-console } else { - console.log('Mail sent: ' + info.response); - transporter.close(); + console.log('Mail sent: ' + info.response) + transporter.close() } - transporter.close(); - }); -}; + transporter.close() + }) +} -export default sendMailToAccount; +export default sendMailToAccount diff --git a/server/util/setup-test-env.js b/server/util/setup-test-env.js index 1eb2c72..a62525a 100644 --- a/server/util/setup-test-env.js +++ b/server/util/setup-test-env.js @@ -1,30 +1,23 @@ // To get normal classnames instead of CSS Modules classnames for testing -require('mock-css-modules'); +require('mock-css-modules') -// Ignore assets -require.extensions['.jpg'] = noop => noop; -require.extensions['.jpeg'] = noop => noop; -require.extensions['.png'] = noop => noop; -require.extensions['.gif'] = noop => noop; +require('@babel/register') +require('babel-polyfill') +require('raf/polyfill') -require('babel-register'); -require('babel-polyfill'); -require('raf/polyfill'); +const { JSDOM } = require('jsdom') +const jsdom = new JSDOM('') +const { window } = jsdom -const { JSDOM } = require('jsdom'); - -const jsdom = new JSDOM(''); -const { window } = jsdom; - -global.window = window; -global.document = window.document; +global.window = window +global.document = window.document global.navigator = { userAgent: 'node.js', -}; +} // use .default export? -const Enzyme = require('enzyme'); -const Adapter = require('enzyme-adapter-react-16'); +const Enzyme = require('enzyme') +const Adapter = require('enzyme-adapter-react-16') -Enzyme.configure({ adapter: new Adapter() }); +Enzyme.configure({ adapter: new Adapter() }) diff --git a/server/util/test-helpers.js b/server/util/test-helpers.js index 380fdf0..a0293b9 100644 --- a/server/util/test-helpers.js +++ b/server/util/test-helpers.js @@ -1,15 +1,15 @@ -import mongoose from 'mongoose'; -import { Mockgoose } from 'mockgoose'; +import mongoose from 'mongoose' +import { Mockgoose } from 'mockgoose' -const mockgoose = new Mockgoose(mongoose); +const mockgoose = new Mockgoose(mongoose) -export async function connectDB() { - await mockgoose.prepareStorage(); +export async function connectDB () { + await mockgoose.prepareStorage() await mongoose.connect('mongodb://localhost:27017/mern-test') - .catch(() => 'Unable to connect to test database'); + .catch(() => 'Unable to connect to test database') } -export async function dropDB() { +export async function dropDB () { await mockgoose.helper.reset() - .catch(() => 'Unable to reset test database'); + .catch(() => 'Unable to reset test database') } diff --git a/server/util/verifyToken.js b/server/util/verifyToken.js index 9402c5c..7401a12 100644 --- a/server/util/verifyToken.js +++ b/server/util/verifyToken.js @@ -1,26 +1,38 @@ -import jwt from 'jsonwebtoken'; -import serverConfig from '../config'; -import { TOKEN_HEADER_NAME } from '../constants'; +import jwt from 'jsonwebtoken' +import serverConfig from '../config' +import { TOKEN_HEADER_NAME } from '../constants' -export default function verifyToken(req, res, next) { - const token = req.headers[TOKEN_HEADER_NAME] || req.query.token; +export default function verifyToken (req, res, next) { + const token = req.headers[TOKEN_HEADER_NAME] || req.query.token if (!token) { if (res !== undefined) { - return res.status(401).send({ auth: false, message: 'Token absent' }); + return res.status(401).send({ + auth: false, + message: 'Token absent', + success: false, + }) } - return next({ status: 401, auth: false, message: 'Token absent' }); + return next({ + status: 401, + auth: false, + message: 'Token absent', + success: false, + }) } try { - const decoded = jwt.verify(token, serverConfig.secret); - req.userId = decoded.id; // eslint-disable-line no-param-reassign - req.userLevel = decoded.level; // eslint-disable-line no-param-reassign - return next(); + const decoded = jwt.verify(token, serverConfig.secret) + req.userId = decoded.id // eslint-disable-line no-param-reassign + req.userLevel = decoded.level // eslint-disable-line no-param-reassign + return next() } catch (error) { return res .status(401) - .send({ auth: false, message: 'Token invalide' }); + .send({ + auth: false, + message: 'Token invalide', + success: false, + }) } - } diff --git a/webpack.config.babel.js b/server/webpack.config.babel.js similarity index 100% rename from webpack.config.babel.js rename to server/webpack.config.babel.js diff --git a/webpack.config.server.js b/server/webpack.config.server.js similarity index 68% rename from webpack.config.server.js rename to server/webpack.config.server.js index b8865ca..2ebaa1d 100644 --- a/webpack.config.server.js +++ b/server/webpack.config.server.js @@ -3,7 +3,7 @@ var path = require('path'); var ExternalsPlugin = require('webpack2-externals-plugin'); module.exports = { - entry: path.resolve(__dirname, 'server/server.js'), + entry: path.resolve(__dirname, 'server.js'), output: { path: __dirname + '/dist/', @@ -34,17 +34,7 @@ module.exports = { loader: 'babel-loader', options: { presets: [ - 'react', - 'es2015', - 'stage-0', - ], - plugins: [ - [ - 'babel-plugin-webpack-loaders', { - config: './webpack.config.babel.js', - verbose: false, - }, - ], + '@babel/preset-env', ], }, }, diff --git a/tests/fake-curl.sh b/tests/fake-curl.sh new file mode 100644 index 0000000..882001d --- /dev/null +++ b/tests/fake-curl.sh @@ -0,0 +1,6 @@ +#!/bin/bash +set -e +exec 3<>/dev/tcp/localhost/80 +echo "GET $1" 1>&3 +response="$(cat <&3)" +echo "$response" diff --git a/tests/test-up-back.sh b/tests/test-up-back.sh new file mode 100755 index 0000000..e6cdf94 --- /dev/null +++ b/tests/test-up-back.sh @@ -0,0 +1,31 @@ +#!/bin/bash +# +# test nginx +# +set -e + +basename=$(basename $0) +echo "# $basename ${APP} ${APP_VERSION}" + +ret=0 +container_name=back + +echo "# Test ${APP}-$container_name up" +set +e +timeout=120; +test_result=1 +dirname=$(dirname $0) +until [ "$timeout" -le 0 -o "$test_result" -eq "0" ] ; do + ${DC} -f ${DC_APP_RUN_ALL_PROD} exec ${DOCKER_COMPOSE_USE_TTY} $container_name curl --retry-max-time 120 --retry-delay 1 --retry 1 -s -X POST http://localhost:8000/api/users/login | grep '"message":' + test_result=$? + echo "Wait $timeout seconds: ${APP}-$container_name up $test_result"; + (( timeout-- )) + sleep 1 +done +if [ "$test_result" -gt "0" ] ; then + ret=$test_result + echo "ERROR: ${APP}-$container_name en erreur" + exit $ret +fi + +exit $ret diff --git a/tests/test-up-candilib.sh b/tests/test-up-candilib.sh index 0680a55..7d53eeb 100644 --- a/tests/test-up-candilib.sh +++ b/tests/test-up-candilib.sh @@ -15,7 +15,7 @@ timeout=120; test_result=1 dirname=$(dirname $0) until [ "$timeout" -le 0 -o "$test_result" -eq "0" ] ; do - curl --fail --retry-max-time 120 --retry-delay 1 --retry 1 http://localhost:80 + curl -L --fail --retry-max-time 120 --retry-delay 1 --retry 1 http://localhost:80 test_result=$? echo "Wait $timeout seconds: ${APP} up $test_result"; (( timeout-- )) diff --git a/tests/test-up-db.sh b/tests/test-up-db.sh index eb8452e..8944875 100755 --- a/tests/test-up-db.sh +++ b/tests/test-up-db.sh @@ -17,7 +17,7 @@ test_result=1 dirname=$(dirname $0) until [ "$timeout" -le 0 -o "$test_result" -eq "0" ] ; do ## TODO: - ${DC} -f ${DC_APP_RUN_PROD} exec ${USE_TTY} $container_name mongo --quiet --eval "JSON.stringify(db.serverStatus({}));" + ${DC} -f ${DC_APP_RUN_ALL_PROD} exec ${DOCKER_COMPOSE_USE_TTY} $container_name mongo --quiet --eval "JSON.stringify(db.serverStatus({}));" test_result=$? echo "Wait $timeout seconds: ${APP}-$container_name up $test_result"; (( timeout-- )) diff --git a/tests/test-up-web.sh b/tests/test-up-web.sh index 4425f1c..cef2f86 100755 --- a/tests/test-up-web.sh +++ b/tests/test-up-web.sh @@ -15,8 +15,9 @@ set +e timeout=120; test_result=1 dirname=$(dirname $0) +docker cp $dirname/fake-curl.sh ${APP}_$container_name:/tmp/ until [ "$timeout" -le 0 -o "$test_result" -eq "0" ] ; do - ${DC} -f ${DC_APP_RUN_PROD} exec ${USE_TTY} $container_name curl --retry-max-time 120 --retry-delay 1 --retry 1 --fail http://localhost:8000 | grep 'Candilib' + ${DC} -f ${DC_APP_RUN_ALL_PROD} exec ${DOCKER_COMPOSE_USE_TTY} $container_name /bin/bash /tmp/fake-curl.sh /candilib/ | grep 'Candilib' test_result=$? echo "Wait $timeout seconds: ${APP}-$container_name up $test_result"; (( timeout-- )) diff --git a/tests/wait-db.sh b/tests/wait-db.sh index bd4e345..d3b67d5 100755 --- a/tests/wait-db.sh +++ b/tests/wait-db.sh @@ -16,7 +16,7 @@ timeout=120; test_result=1 dirname=$(dirname $0) until [ "$timeout" -le 0 -o "$test_result" -eq "0" ] ; do - ${DC} -f ${DC_APP_RUN_PROD} exec ${USE_TTY} $container_name mongo --eval "print(\"waited for connection\")" + ${DC} -f ${DC_APP_RUN_ALL_PROD} exec ${DOCKER_COMPOSE_USE_TTY} $container_name mongo --eval "print(\"waited for connection\")" test_result=$? echo "Wait $timeout seconds: ${APP}-$container_name up $test_result"; (( timeout-- )) diff --git a/webpack.config.dev.js b/webpack.config.dev.js deleted file mode 100644 index aaf409a..0000000 --- a/webpack.config.dev.js +++ /dev/null @@ -1,108 +0,0 @@ -var webpack = require('webpack'); -var postcssPresetEnv = require('postcss-preset-env'); -var postcssFocus = require('postcss-focus'); -var postcssReporter = require('postcss-reporter'); - -module.exports = { - devtool: 'cheap-module-eval-source-map', - - entry: { - app: [ - 'eventsource-polyfill', - 'webpack-hot-middleware/client', - 'webpack/hot/only-dev-server', - 'react-hot-loader/patch', - './client/index.js', - ], - vendor: [ - 'react', - 'react-dom', - ], - }, - - output: { - path: __dirname, - filename: 'app.js', - }, - - resolve: { - extensions: ['.js', '.jsx'], - modules: [ - 'client', - 'node_modules', - ], - }, - - module: { - rules: [ - { - test: /\.s?css$/, - exclude: /node_modules/, - use: [ - { - loader: 'style-loader', - }, - { - loader: 'css-loader', - options: { - localIdentName: '[name]__[local]__[hash:base64:5]', - modules: true, - importLoaders: 1, - sourceMap: true, - }, - }, - { - loader: 'postcss-loader', - options: { - plugins: () => [ - postcssFocus(), - postcssPresetEnv({ - browsers: ['last 2 versions', 'IE > 10'], - }), - postcssReporter({ - clearMessages: true, - }), - ], - }, - }, - ], - }, - { - test: /\.css$/, - include: /node_modules/, - use: ['style-loader', 'css-loader'], - }, - { - test: /\.jsx*$/, - exclude: [/node_modules/, /.+\.config.js/], - use: 'babel-loader', - }, - { - test: /\.(jpe?g|gif|png|svg)$/i, - use: [ - { - loader: 'url-loader', - options: { - limit: 10000, - }, - }, - ], - }, - ], - }, - - plugins: [ - new webpack.HotModuleReplacementPlugin(), - new webpack.optimize.CommonsChunkPlugin({ - name: 'vendor', - minChunks: Infinity, - filename: 'vendor.js', - }), - new webpack.DefinePlugin({ - 'process.env': { - CLIENT: JSON.stringify(true), - 'NODE_ENV': JSON.stringify('development'), - } - }), - ], -}; diff --git a/webpack.config.prod.js b/webpack.config.prod.js deleted file mode 100644 index fb5ba1d..0000000 --- a/webpack.config.prod.js +++ /dev/null @@ -1,122 +0,0 @@ -var webpack = require('webpack'); -var ExtractTextPlugin = require('extract-text-webpack-plugin'); -var ManifestPlugin = require('webpack-manifest-plugin'); -var ChunkManifestPlugin = require('chunk-manifest-webpack-plugin'); -var postcssPresetEnv = require('postcss-preset-env'); -var postcssFocus = require('postcss-focus'); -var postcssReporter = require('postcss-reporter'); -const UglifyJsPlugin = require('uglifyjs-webpack-plugin') -var cssnano = require('cssnano'); - -module.exports = { - devtool: 'hidden-source-map', - - entry: { - app: [ - './client/index.js', - ], - vendor: [ - 'react', - 'react-dom', - ] - }, - - output: { - path: __dirname + '/dist/client/', - filename: '[name].[chunkhash].js', - publicPath: '/', - }, - - resolve: { - extensions: ['.js', '.jsx'], - modules: [ - 'client', - 'node_modules', - ], - }, - - module: { - rules: [ - { - test: /\.s?css$/, - exclude: /node_modules/, - use: ExtractTextPlugin.extract({ - fallback: 'style-loader', - use: [ - { - loader: 'css-loader', - options: { - localIdentName: '[hash:base64]', - modules: true, - importLoaders: 1, - }, - }, - { - loader: 'postcss-loader', - options: { - plugins: () => [ - postcssFocus(), - postcssPresetEnv({ - browsers: ['last 2 versions', 'IE > 10'], - }), - cssnano({ - autoprefixer: false, - }), - postcssReporter({ - clearMessages: true, - }), - ], - }, - }, - ], - }), - }, - { - test: /\.css$/, - include: /node_modules/, - use: ['style-loader', 'css-loader'], - }, - { - test: /\.jsx*$/, - exclude: /node_modules/, - use: 'babel-loader', - }, - { - test: /\.(jpe?g|gif|png|svg)$/i, - use: [ - { - loader: 'url-loader', - options: { - limit: 10000, - }, - }, - ], - }, - ], - }, - - plugins: [ - new webpack.DefinePlugin({ - 'process.env': { - 'NODE_ENV': JSON.stringify('production'), - } - }), - new webpack.optimize.CommonsChunkPlugin({ - name: 'vendor', - minChunks: Infinity, - filename: 'vendor.js', - }), - new ExtractTextPlugin({ - filename: 'app.[contenthash].css', - allChunks: true, - }), - new ManifestPlugin({ - basePath: '/', - }), - new ChunkManifestPlugin({ - filename: "chunk-manifest.json", - manifestVariable: "webpackManifest", - }), - new UglifyJsPlugin(), - ], -};