diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 10605db..0a539bd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: - node-version: [14.x] + node-version: [20.x] services: mongo: @@ -21,23 +21,23 @@ jobs: - 27017:27017 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v1 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} - name: Cache Node.js modules - uses: actions/cache@v1 + uses: actions/cache@v4 with: - path: ~/.npm + path: ~/.npm # npm cache files are stored in `~/.npm` on Linux/macOS key: ${{ runner.OS }}-node-${{ matrix.node-version }}-${{ hashFiles('**/package-lock.json') }} restore-keys: | ${{ runner.OS }}-node-${{ matrix.node-version }}- ${{ runner.OS }}-node- ${{ runner.OS }}- - - run: npm install - - run: npm test + - run: yarn install + - run: yarn test env: CI: true FACEBOOK_APP_ID: ${{ secrets.FACEBOOK_APP_ID }} @@ -61,7 +61,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Prepare id: prep run: | @@ -78,19 +78,19 @@ jobs: if [ "${{ github.event_name }}" = "push" ]; then TAGS="$TAGS,${DOCKER_IMAGE}:sha-${GITHUB_SHA::8}" fi - echo ::set-output name=version::${VERSION} - echo ::set-output name=tags::${TAGS} - echo ::set-output name=created::$(date -u +'%Y-%m-%dT%H:%M:%SZ') - echo ::set-output name=short_sha::${GITHUB_SHA::8} + echo "version=${VERSION}" >> $GITHUB_OUTPUT + echo "tags=${TAGS}" >> $GITHUB_OUTPUT + echo "created=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT + echo "short_sha=${GITHUB_SHA::8}" >> $GITHUB_OUTPUT - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v3 - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v5 with: context: . file: ./client/Dockerfile.prod @@ -111,7 +111,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Prepare id: prep run: | @@ -128,18 +128,18 @@ jobs: if [ "${{ github.event_name }}" = "push" ]; then TAGS="$TAGS,${DOCKER_IMAGE}:sha-${GITHUB_SHA::8}" fi - echo ::set-output name=version::${VERSION} - echo ::set-output name=tags::${TAGS} - echo ::set-output name=created::$(date -u +'%Y-%m-%dT%H:%M:%SZ') + echo "version=${VERSION}" >> $GITHUB_OUTPUT + echo "tags=${TAGS}" >> $GITHUB_OUTPUT + echo "created=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v3 - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v5 with: context: . file: ./server/Dockerfile.prod @@ -156,7 +156,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Prepare id: prep run: | @@ -173,18 +173,18 @@ jobs: if [ "${{ github.event_name }}" = "push" ]; then TAGS="$TAGS,${DOCKER_IMAGE}:sha-${GITHUB_SHA::8}" fi - echo ::set-output name=version::${VERSION} - echo ::set-output name=tags::${TAGS} - echo ::set-output name=created::$(date -u +'%Y-%m-%dT%H:%M:%SZ') + echo "version=${VERSION}" >> $GITHUB_OUTPUT + echo "tags=${TAGS}" >> $GITHUB_OUTPUT + echo "created=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 + uses: docker/setup-buildx-action@v3 - name: Login to DockerHub - uses: docker/login-action@v1 + uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push - uses: docker/build-push-action@v2 + uses: docker/build-push-action@v5 with: context: . file: ./nginx-proxy/Dockerfile.prod diff --git a/client/Dockerfile b/client/Dockerfile index d77f8d5..f36316c 100644 --- a/client/Dockerfile +++ b/client/Dockerfile @@ -1,5 +1,5 @@ FROM node:lts-buster WORKDIR /mern-stack/client -COPY ./client/package*.json ./ -RUN npm install -CMD ["npm", "run", "start"] \ No newline at end of file +COPY ./client/package*.json ./client/yarn.lock ./ +RUN yarn install +CMD ["yarn", "start"] \ No newline at end of file diff --git a/client/src/store/actions/auth.actions.js b/client/src/store/actions/auth.actions.js index 069d707..b76661d 100644 --- a/client/src/store/actions/auth.actions.js +++ b/client/src/store/actions/auth.actions.js @@ -1,116 +1,118 @@ import { replace } from 'connected-react-router'; import * as actionTypes from './types'; -export const signUp = (formValues) => (dispatch, getState, { mernApi }) => { - dispatch({ type: actionTypes.SIGN_UP }); - return mernApi.post('/api/auth/signup', formValues).then( - (response) => { - dispatch({ type: actionTypes.SIGN_UP_SUCCESS }); - }, - (err) => { - dispatch({ - type: actionTypes.SIGN_UP_FAIL, - payload: err.response.data.error.message, - }); - } - ); -}; +export const signUp = + (formValues) => + (dispatch, getState, { mernApi }) => { + dispatch({ type: actionTypes.SIGN_UP }); + return mernApi.post('/api/auth/signup', formValues).then( + (response) => { + dispatch({ type: actionTypes.SIGN_UP_SUCCESS }); + }, + (err) => { + dispatch({ + type: actionTypes.SIGN_UP_FAIL, + payload: err?.response?.data?.error?.message, + }); + } + ); + }; -export const signIn = (formValues) => (dispatch, getState, { mernApi }) => { - dispatch({ type: actionTypes.SIGN_IN }); - return signInHelper( - '/api/auth/signin', - formValues, - actionTypes.SIGN_IN_SUCCESS, - actionTypes.SIGN_IN_FAIL, - dispatch, - mernApi - ); -}; +export const signIn = + (formValues) => + (dispatch, getState, { mernApi }) => { + dispatch({ type: actionTypes.SIGN_IN }); + return signInHelper( + '/api/auth/signin', + formValues, + actionTypes.SIGN_IN_SUCCESS, + actionTypes.SIGN_IN_FAIL, + dispatch, + mernApi + ); + }; -export const facebookSignIn = (formValues) => ( - dispatch, - getState, - { mernApi } -) => { - dispatch({ type: actionTypes.FACEBOOK_SIGN_IN }); - return signInHelper( - '/api/auth/facebook', - formValues, - actionTypes.FACEBOOK_SIGN_IN_SUCCESS, - actionTypes.FACEBOOK_SIGN_IN_FAIL, - dispatch, - mernApi - ); -}; +export const facebookSignIn = + (formValues) => + (dispatch, getState, { mernApi }) => { + dispatch({ type: actionTypes.FACEBOOK_SIGN_IN }); + return signInHelper( + '/api/auth/facebook', + formValues, + actionTypes.FACEBOOK_SIGN_IN_SUCCESS, + actionTypes.FACEBOOK_SIGN_IN_FAIL, + dispatch, + mernApi + ); + }; -export const googleSignIn = (formValues) => ( - dispatch, - getState, - { mernApi } -) => { - dispatch({ type: actionTypes.GOOGLE_SIGN_IN }); - return signInHelper( - '/api/auth/google', - formValues, - actionTypes.GOOGLE_SIGN_IN_SUCCESS, - actionTypes.GOOGLE_SIGN_IN_FAIL, - dispatch, - mernApi - ); -}; +export const googleSignIn = + (formValues) => + (dispatch, getState, { mernApi }) => { + dispatch({ type: actionTypes.GOOGLE_SIGN_IN }); + return signInHelper( + '/api/auth/google', + formValues, + actionTypes.GOOGLE_SIGN_IN_SUCCESS, + actionTypes.GOOGLE_SIGN_IN_FAIL, + dispatch, + mernApi + ); + }; -export const tryLocalSignIn = () => (dispatch, getState, { mernApi }) => { - dispatch({ type: actionTypes.TRY_LOCAL_SIGN_IN }); - try { - const authInfo = JSON.parse(localStorage.getItem('authInfo')); - const now = Math.floor(Date.now() / 1000); - if (!authInfo || (authInfo && authInfo.expiresAt <= now)) { - return Promise.resolve().then(() => { - dispatch(tryLocalSignInFail()); - }); - } - mernApi.setAuthToken(authInfo.token); - return mernApi - .post('/api/auth/verify-jwt-token', { - refreshToken: true, - refreshUser: true, - }) - .then( - (response) => { - authInfo.token = response.data.token; - authInfo.expiresAt = response.data.expiresAt; - authInfo.user = response.data.user; - dispatch( - signInSuccess(authInfo, actionTypes.TRY_LOCAL_SIGN_IN_SUCCESS) - ); - }, - (err) => { +export const tryLocalSignIn = + () => + (dispatch, getState, { mernApi }) => { + dispatch({ type: actionTypes.TRY_LOCAL_SIGN_IN }); + try { + const authInfo = JSON.parse(localStorage.getItem('authInfo')); + const now = Math.floor(Date.now() / 1000); + if (!authInfo || (authInfo && authInfo.expiresAt <= now)) { + return Promise.resolve().then(() => { dispatch(tryLocalSignInFail()); - } - ); - } catch (err) { - dispatch(tryLocalSignInFail(err)); - } -}; + }); + } + mernApi.setAuthToken(authInfo.token); + return mernApi + .post('/api/auth/verify-jwt-token', { + refreshToken: true, + refreshUser: true, + }) + .then( + (response) => { + authInfo.token = response.data.token; + authInfo.expiresAt = response.data.expiresAt; + authInfo.user = response.data.user; + dispatch( + signInSuccess(authInfo, actionTypes.TRY_LOCAL_SIGN_IN_SUCCESS) + ); + }, + (err) => { + dispatch(tryLocalSignInFail()); + } + ); + } catch (err) { + dispatch(tryLocalSignInFail(err)); + } + }; -const signInSuccess = (payload, successType) => ( - dispatch, - getState, - { mernApi } -) => { - setAuthInfo(payload, mernApi); - dispatch({ type: successType, payload }); - if (getState().auth.attemptedPath) { - dispatch(replace(getState().auth.attemptedPath)); - dispatch(setAttemptedPath(null)); - } -}; +const signInSuccess = + (payload, successType) => + (dispatch, getState, { mernApi }) => { + setAuthInfo(payload, mernApi); + dispatch({ type: successType, payload }); + if (getState().auth.attemptedPath) { + dispatch(replace(getState().auth.attemptedPath)); + dispatch(setAttemptedPath(null)); + } + }; -const tryLocalSignInFail = () => (dispatch, getState, { mernApi }) => { - clearAuthInfo(mernApi); - dispatch({ type: actionTypes.TRY_LOCAL_SIGN_IN_FAIL }); -}; +const tryLocalSignInFail = + () => + (dispatch, getState, { mernApi }) => { + clearAuthInfo(mernApi); + dispatch({ type: actionTypes.TRY_LOCAL_SIGN_IN_FAIL }); + }; export const setAttemptedPath = (path) => { return { @@ -119,30 +121,30 @@ export const setAttemptedPath = (path) => { }; }; -export const signOut = () => (dispatch, getState, { mernApi }) => { - dispatch({ type: actionTypes.SIGN_OUT }); - clearAuthInfo(mernApi); - dispatch({ type: actionTypes.SIGN_OUT_SUCCESS }); -}; +export const signOut = + () => + (dispatch, getState, { mernApi }) => { + dispatch({ type: actionTypes.SIGN_OUT }); + clearAuthInfo(mernApi); + dispatch({ type: actionTypes.SIGN_OUT_SUCCESS }); + }; -export const verifyEmail = (formValues, token) => ( - dispatch, - getState, - { mernApi } -) => { - dispatch({ type: actionTypes.VERIFY_EMAIL }); - return mernApi.post(`/api/auth/verify-email/${token}`, formValues).then( - (response) => { - dispatch({ type: actionTypes.VERIFY_EMAIL_SUCCESS }); - }, - (err) => { - dispatch({ - type: actionTypes.VERIFY_EMAIL_FAIL, - payload: err.response.data.error.message, - }); - } - ); -}; +export const verifyEmail = + (formValues, token) => + (dispatch, getState, { mernApi }) => { + dispatch({ type: actionTypes.VERIFY_EMAIL }); + return mernApi.post(`/api/auth/verify-email/${token}`, formValues).then( + (response) => { + dispatch({ type: actionTypes.VERIFY_EMAIL_SUCCESS }); + }, + (err) => { + dispatch({ + type: actionTypes.VERIFY_EMAIL_FAIL, + payload: err?.response?.data?.error?.message, + }); + } + ); + }; export const requestVerificationEmail = (formValues) => { return (dispatch, getState, { mernApi }) => { @@ -154,7 +156,7 @@ export const requestVerificationEmail = (formValues) => { (err) => { dispatch({ type: actionTypes.REQUEST_VERIFICATION_EMAIL_FAIL, - payload: err.response.data.error.message, + payload: err?.response?.data?.error?.message, }); } ); @@ -171,7 +173,7 @@ export const requestPasswordReset = (formValues) => { (err) => { dispatch({ type: actionTypes.REQUEST_PASSWORD_RESET_FAIL, - payload: err.response.data.error.message, + payload: err?.response?.data?.error?.message, }); } ); @@ -188,7 +190,7 @@ export const resetPassword = (formValues, token) => { (err) => dispatch({ type: actionTypes.RESET_PASSWORD_FAIL, - payload: err.response.data.error.message, + payload: err?.response?.data?.error?.message, }) ); }; @@ -213,7 +215,10 @@ const signInHelper = ( dispatch(signInSuccess(response.data, successType)); }, (err) => { - dispatch({ type: failType, payload: err.response.data.error.message }); + dispatch({ + type: failType, + payload: err?.response?.data?.error?.message, + }); } ); }; diff --git a/docker-compose.local.yml b/docker-compose.local.yml index 04ccd72..f631403 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -1,4 +1,3 @@ -version: '3.8' services: mongo: # Remove this if you use external MongoDB service such as MongoDB Atlas restart: always diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index fe914f4..6523623 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -1,4 +1,3 @@ -version: '3.8' services: api-server: restart: always diff --git a/docker-compose.yml b/docker-compose.yml index c301974..d9fef04 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,3 @@ -version: '3.8' services: mongo: image: mongo:latest @@ -29,6 +28,8 @@ services: stdin_open: true # FIXME: https://github.com/facebook/create-react-app/issues/8688 ports: - '${PORT}:${PORT}' + extra_hosts: + - "localhost:host-gateway" depends_on: - api-server