From fecce76fe130c2068ce382a8321a94c5f15b9f0f Mon Sep 17 00:00:00 2001 From: danysmall Date: Sat, 12 Nov 2022 23:41:31 +0300 Subject: [PATCH 1/8] Improved loading content of and article with saving them in redux store When article was loaded once than it will be cached in redux store. On the next loading of article content it will be get from store --- Dockerfile | 47 ++++++++++++------- scripts/entrypoint.sh | 3 ++ src/article/controller/articleViewModel.ts | 42 ++++++----------- src/article/data/articleAPIService.ts | 4 +- src/article/data/articleActions.ts | 29 +++++++----- src/article/data/articleCommonStateStore.ts | 47 ++++++++++++++----- src/article/data/articleReducer.ts | 7 +-- .../data/articleStoreImplementation.ts | 9 ++-- src/article/domain/articleStore.ts | 7 +-- src/article/useCases/fetchArticleUseCase.ts | 28 ++++++++++- src/article/useCases/getArticleUseCase.ts | 23 +++++++++ src/components/fetchAnArticle/AnArticle.tsx | 19 +++----- .../fetchAnArticle/AnArticleBody.tsx | 14 ++---- 13 files changed, 173 insertions(+), 106 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9a98d4f..ec52764 100755 --- a/Dockerfile +++ b/Dockerfile @@ -1,30 +1,43 @@ -# Install dependencies only when needed -FROM node:fermium-alpine AS deps -# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. +# Install dependencies of project +FROM node:fermium-alpine AS dependencies RUN apk add --no-cache libc6-compat WORKDIR /home/app/ -COPY package.json package-lock.json ./ -RUN npm ci +COPY package.json ./ +RUN npm i -# Bundle static assets with nginx -FROM node:fermium-alpine as production - -# Copy built assets from builder +# Build application to bunch of static files +FROM node:fermium-alpine AS builder WORKDIR /home/app/ -COPY --from=deps ./home/app/node_modules ./node_modules +COPY --from=dependencies ./home/app/node_modules ./node_modules COPY . . +RUN npm run build + + +# NGINX image +FROM nginx:1.21.6 as production +# Copy built assets from builder +COPY --from=builder /home/app/build /usr/share/nginx/html +# Add nginx.config +COPY nginx.conf /etc/nginx/conf.d/default.conf +# Copy setup nginx entrypoint file +COPY ./scripts/entrypoint.sh . # Expose ports -EXPOSE 3000 +EXPOSE 80 + +# Execute script +RUN chmod +x ./entrypoint.sh ENV NODE_ENV production -ENV USER_NAME=node_user USER_UID=2000 GROUP_NAME=node_group GROUP_UID=2000 +# ENV USER_NAME=node_user USER_UID=2000 GROUP_NAME=node_group GROUP_UID=2000 -RUN deluser --remove-home node \ - && addgroup --g ${GROUP_UID} -S ${GROUP_NAME} \ - && adduser -D -S -s /sbin/nologin -u ${USER_UID} -G ${GROUP_NAME} ${USER_NAME} -USER "${USER_NAME}" +# RUN deluser --remove-home node \ +# && addgroup --g ${GROUP_UID} -S ${GROUP_NAME} \ +# && adduser -D -S -s /sbin/nologin -u ${USER_UID} -G ${GROUP_NAME} ${USER_NAME} +# USER "${USER_NAME}" +ENTRYPOINT ["./entrypoint.sh"] -ENTRYPOINT [ "npm","start"] \ No newline at end of file +# Start serving +CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file diff --git a/scripts/entrypoint.sh b/scripts/entrypoint.sh index 05eccfd..d6ba134 100755 --- a/scripts/entrypoint.sh +++ b/scripts/entrypoint.sh @@ -1,6 +1,9 @@ #! /bin/bash # no verbose set +x +# save env to file +printenv > .env.production + # config envFilename='.env.production' resolvingPath='/usr/share/nginx/html' diff --git a/src/article/controller/articleViewModel.ts b/src/article/controller/articleViewModel.ts index b17e336..c7340c6 100755 --- a/src/article/controller/articleViewModel.ts +++ b/src/article/controller/articleViewModel.ts @@ -1,44 +1,30 @@ import type { ArticleStore } from "../domain/articleStore"; import { useCallback, useEffect } from "react"; -import { getArticle } from "article/data/articleAPIService"; import { Article } from "article/domain/articleEntity"; +import { GetArticleUseCase } from "article/useCases/getArticleUseCase"; +import { FetchArticleUseCase } from "article/useCases/fetchArticleUseCase"; +import { useParams } from "react-router-dom"; function useArticleViewModel( store: ArticleStore, - fetchArticleUseCase: ( - fetchArticleCallback: (id: string) => Promise
, - setArticle: ArticleStore["setArticle"], - id: string, - ) => Promise
, - getArticleUseCase: ( - getArticle: ArticleStore["getArticle"], - setArticle: ArticleStore["setArticle"], - id: Article["id"], - ) => Promise
, - id: string, + fetchArticleUseCase: FetchArticleUseCase, + getArticleUseCase: GetArticleUseCase, ) { - let article: Article | null | undefined; + const { id } = useParams(); - const _getArticle = useCallback( - (id: string) => { - getArticleUseCase(store.getArticle, store.setArticle, id).then(async (value) => { - if (value == null) { - article = await fetchArticleUseCase(getArticle, store.setArticle, id); - return; - } - article = value; - }); + const getArticle = useCallback( + () => { + getArticleUseCase.call(id ?? '').catch((_) => fetchArticleUseCase.call(id ?? '')); + console.log(id); }, - [store.setArticle] + [id] ); - useEffect(() => { - _getArticle(id); - }, [article]); + useEffect(getArticle, []); return { - article: store.article, - shouldShowLoading: typeof store.article === "undefined" || store.isLoading, + article: store.currentArticle, + shouldShowLoading: store.isLoading, hasError: store.hasError, }; } diff --git a/src/article/data/articleAPIService.ts b/src/article/data/articleAPIService.ts index e8c830b..d24bb38 100755 --- a/src/article/data/articleAPIService.ts +++ b/src/article/data/articleAPIService.ts @@ -7,7 +7,7 @@ import { integratorApiClient } from "core/httpClient"; const articleEndpoint = "/papers/" -async function getArticle(id: string): Promise
{ +async function fetchArticle(id: string): Promise
{ try { const response = await integratorApiClient.get( // `https://run.mocky.io/v3/62cd4581-d864-4d46-b1d6-02b45b5d1994/${id}` @@ -33,4 +33,4 @@ async function getArticle(id: string): Promise
{ } } -export { getArticle }; +export { fetchArticle }; diff --git a/src/article/data/articleActions.ts b/src/article/data/articleActions.ts index 0a56176..56dd7bf 100755 --- a/src/article/data/articleActions.ts +++ b/src/article/data/articleActions.ts @@ -1,22 +1,25 @@ import type { Article } from "../domain/articleEntity"; -import { getArticle as getArticleAPI } from "./articleAPIService"; import * as actionTypes from "./articleActionTypes"; import { dispatchStatus } from "../../store/index"; -const setArticleAction = (article: Article) => (dispatch: any) => - dispatch({ type: actionTypes.SET_ARTICLE, article }); +const setArticleAction = (article: Article, articlesList: Array
) => (dispatch: any) => { + if (!articlesList.includes(article)) { + const updatedList = articlesList.concat([article]); + dispatch({ type: actionTypes.SET_ARTICLE, article, updatedList }); + } +} -const getArticleAction = (id: string) => (dispatch: any) => { +const getArticleAction = (id: string, articles: Array
) => (dispatch: any) => { + const filteredArray = articles.filter((e) => e.id == id); + if (filteredArray.length > 0) { + const article = filteredArray[0]; + dispatchStatus(actionTypes.GET_ARTICLE, ".success", article)(dispatch); + return article; + } - return getArticleAPI(id) - .then((article) => { - dispatchStatus(actionTypes.GET_ARTICLE, ".success", article)(dispatch); - return article; - }) - .catch((reason) => { - dispatchStatus(actionTypes.GET_ARTICLE, ".failure", reason)(dispatch); - return reason; - }); + const reason = 'Article not in the store'; + dispatchStatus(actionTypes.GET_ARTICLE, ".failure", reason)(dispatch); + return null; }; export { setArticleAction, getArticleAction }; diff --git a/src/article/data/articleCommonStateStore.ts b/src/article/data/articleCommonStateStore.ts index bee210f..fdf3cbd 100755 --- a/src/article/data/articleCommonStateStore.ts +++ b/src/article/data/articleCommonStateStore.ts @@ -2,37 +2,58 @@ import React, { useCallback, useState } from "react"; import { useDispatch } from "react-redux"; import { ArticleStore } from "../domain/articleStore"; import type { Article } from "../domain/articleEntity"; -import { getArticle as getArticleAPI } from "./articleAPIService"; const useArticleCommonStore = (): ArticleStore => { const [isLoading, setLoading] = useState(false); const [hasError, setError] = useState(false); - const [article, setArticleState] = useState
(); + const [currentArticle, setCurrentArticle] = useState
(null); + const [articles, setArticlesState] = useState>([]); const dispatch = useDispatch(); const getArticle = useCallback( - async (id: string) => { + (id: string) => { setLoading(true); - try { - const article = await getArticleAPI(id); - setArticleState(article); + if (typeof currentArticle === undefined) { + const fromStore = findArticleFromStore(id); + if (typeof fromStore === undefined) { + setError(true); + return null; + } setLoading(false); - return article; - } catch (error) { - setError(true); - return null; + setCurrentArticle(fromStore); + return fromStore; } + setLoading(false); + return currentArticle; }, [dispatch] ); + const findArticleFromStore = (id: string): Article | null => { + const filteredArray = articles.filter((e) => e.id == id); + if (filteredArray.length > 0) { + return filteredArray[0]; + } + return null; + } + + const setNewArticle = (newArticle: Article) => { + setCurrentArticle(newArticle); + if (!articles.includes(newArticle)) { + setArticlesState(articles.concat([newArticle])); + } else if (articles.length == 0) { + setArticlesState([newArticle]); + } + } + return { - article: article, + articles: articles, + currentArticle: currentArticle, isLoading, hasError, - setArticle: setArticleState, - getArticle, + setArticle: setNewArticle, + getArticle: getArticle, }; }; diff --git a/src/article/data/articleReducer.ts b/src/article/data/articleReducer.ts index bdc5d45..b5ac846 100755 --- a/src/article/data/articleReducer.ts +++ b/src/article/data/articleReducer.ts @@ -5,7 +5,8 @@ import * as actionTypes from "./articleActionTypes"; type ArticleStoreState = Omit; const INITIAL_STATE: ArticleStoreState = { - article: undefined, + articles: [], + currentArticle: null, isLoading: false, hasError: false, }; @@ -16,11 +17,11 @@ const articleReducer = ( ): ArticleStoreState => { switch (action.type) { case actionTypes.SET_ARTICLE: - return { ...state, article: action.article }; + return { ...state, articles: action.updatedList, currentArticle: action.article }; case actionTypes.GET_ARTICLE: return { ...state, isLoading: true }; case actionTypes.GET_ARTICLE_SUCCESS: - return { ...state, isLoading: false, article: action.payload }; + return { ...state, isLoading: false, currentArticle: action.payload }; case actionTypes.GET_ARTICLE_FAILURE: return { ...state, hasError: true, isLoading: false }; default: diff --git a/src/article/data/articleStoreImplementation.ts b/src/article/data/articleStoreImplementation.ts index 8d19b97..76c384e 100755 --- a/src/article/data/articleStoreImplementation.ts +++ b/src/article/data/articleStoreImplementation.ts @@ -9,22 +9,23 @@ import { RootState, useAppSelector } from "store"; const articleSelector = (state: RootState): ArticleStoreState => state.article; const useArticleStore = (): ArticleStore => { - const { isLoading, article, hasError } = useAppSelector(articleSelector); + const { isLoading, hasError, currentArticle, articles } = useAppSelector(articleSelector); const dispatch = useDispatch(); const setArticle = useCallback( - (article: Article) => setArticleAction(article)(dispatch), + (article: Article) => setArticleAction(article, articles)(dispatch), [dispatch] ); const getArticle = useCallback( - (id: string) => getArticleAction(id)(dispatch), + (id: string) => getArticleAction(id, articles)(dispatch), [dispatch] ); return { - article: article, + articles: articles, + currentArticle: currentArticle, isLoading, hasError, setArticle, diff --git a/src/article/domain/articleStore.ts b/src/article/domain/articleStore.ts index 4ec1805..68546e7 100755 --- a/src/article/domain/articleStore.ts +++ b/src/article/domain/articleStore.ts @@ -1,13 +1,14 @@ import { Article } from './articleEntity'; interface ArticleStore { // State - article: Article | undefined; + articles: Array
; + currentArticle: Article | null; isLoading: boolean; hasError: boolean; // Actions - setArticle(article?: Article): void; - getArticle(identifier: string): Promise
; + setArticle(article: Article): void; + getArticle(identifier: string): Article | null; } export type { ArticleStore }; diff --git a/src/article/useCases/fetchArticleUseCase.ts b/src/article/useCases/fetchArticleUseCase.ts index a3b646d..30fb48f 100644 --- a/src/article/useCases/fetchArticleUseCase.ts +++ b/src/article/useCases/fetchArticleUseCase.ts @@ -1,6 +1,32 @@ import { Article } from "article/domain/articleEntity"; import { ArticleStore } from "article/domain/articleStore"; + +class FetchArticleUseCase { + /* ------------------------------ Dependencies ------------------------------ */ + _fetchArticleCallback: (id: string) => Promise
; + _store: ArticleStore; + /* -------------------------------------------------------------------------- */ + constructor( + fetchArticle: (id: string) => Promise
, + store: ArticleStore, + ) { + this._fetchArticleCallback = fetchArticle; + this._store = store; + } + /* ----------------------------- Implementation ----------------------------- */ + async call(id: string): Promise
{ + return this._fetchArticleCallback(id).then((article) => { + if (article != null) { + this._store.setArticle(article); + } + return article; + }) + } + /* -------------------------------------------------------------------------- */ +} + + const fetchArticleUseCase = async ( fetchArticleCallback: (id: string) => Promise
, setArticle: ArticleStore["setArticle"], @@ -12,5 +38,5 @@ const fetchArticleUseCase = async ( } return article; }; - +export { FetchArticleUseCase }; export { fetchArticleUseCase }; diff --git a/src/article/useCases/getArticleUseCase.ts b/src/article/useCases/getArticleUseCase.ts index 9a691c2..e4d560e 100755 --- a/src/article/useCases/getArticleUseCase.ts +++ b/src/article/useCases/getArticleUseCase.ts @@ -1,6 +1,28 @@ import { Article } from "article/domain/articleEntity"; import type { ArticleStore } from "../domain/articleStore"; +class GetArticleUseCase { + /* ------------------------------ Dependencies ------------------------------ */ + _store: ArticleStore; + /* -------------------------------------------------------------------------- */ + constructor( + store: ArticleStore, + ) { + this._store = store; + } + /* ----------------------------- Implementation ----------------------------- */ + async call(id: string): Promise
{ + const storedArticle = this._store.getArticle(id); + if (storedArticle != null) { + this._store.setArticle(storedArticle); + return storedArticle; + } + throw new Error('Article not found'); + } + /* -------------------------------------------------------------------------- */ +} + + const getArticleUseCase = async ( getArticle: ArticleStore["getArticle"], setArticle: ArticleStore["setArticle"], @@ -13,4 +35,5 @@ const getArticleUseCase = async ( return article; }; +export { GetArticleUseCase }; export { getArticleUseCase }; diff --git a/src/components/fetchAnArticle/AnArticle.tsx b/src/components/fetchAnArticle/AnArticle.tsx index 5b680b5..98c3e36 100755 --- a/src/components/fetchAnArticle/AnArticle.tsx +++ b/src/components/fetchAnArticle/AnArticle.tsx @@ -10,27 +10,20 @@ import { SVGSearch } from "components/icons"; import BaseLayout from "components/BaseLayout"; import Typography from "components/typography/Typography"; import { useTranslation } from "react-i18next"; -import { fetchArticleUseCase } from "article/useCases/fetchArticleUseCase"; -import { getArticleUseCase } from "article/useCases/getArticleUseCase"; +import { FetchArticleUseCase } from "article/useCases/fetchArticleUseCase"; +import { GetArticleUseCase } from "article/useCases/getArticleUseCase"; +import { fetchArticle } from "article/data/articleAPIService"; const AnArticle = () => { const store = useArticleStore(); - const { id } = useParams(); const { article, hasError, shouldShowLoading } = useArticleViewModel( store, - fetchArticleUseCase, - getArticleUseCase, - id ?? '', + new FetchArticleUseCase(fetchArticle, store), + new GetArticleUseCase(store), ); + const { i18n, t } = useTranslation(); - // const { id } = useParams(); - // const newId = `${id}`; - - // useEffect(() => { - // store.getArticle(newId); - // }, [id]); - if (hasError) { return ; } diff --git a/src/components/fetchAnArticle/AnArticleBody.tsx b/src/components/fetchAnArticle/AnArticleBody.tsx index c849a6c..ed9efeb 100755 --- a/src/components/fetchAnArticle/AnArticleBody.tsx +++ b/src/components/fetchAnArticle/AnArticleBody.tsx @@ -13,21 +13,17 @@ import BaseLayout from "components/BaseLayout"; import Container from "components/Container"; import NotFound from "./NotFound"; import Markdown from "components/Markdown"; -import { fetchArticleUseCase } from "article/useCases/fetchArticleUseCase"; -import { getArticleUseCase } from "article/useCases/getArticleUseCase"; +import { FetchArticleUseCase } from "article/useCases/fetchArticleUseCase"; +import { GetArticleUseCase } from "article/useCases/getArticleUseCase"; +import { fetchArticle } from "article/data/articleAPIService"; const AnArticleBody = () => { const store = useArticleStore(); - const { id } = useParams(); const { article, hasError, shouldShowLoading } = useArticleViewModel( store, - fetchArticleUseCase, - getArticleUseCase, - id ?? '', + new FetchArticleUseCase(fetchArticle, store), + new GetArticleUseCase(store), ); - // useEffect(() => { - // store.getArticle(newId); - // }, [id]); if (hasError) ; return ( -- 2.39.5 From e20831fbfc545f48a3e7ad3c7640f385e6f814d2 Mon Sep 17 00:00:00 2001 From: danysmall Date: Sat, 12 Nov 2022 23:55:00 +0300 Subject: [PATCH 2/8] Fixed 404 error on "article/info/$id" pages --- src/article/controller/articleViewModel.ts | 19 ++++++++++--------- src/article/data/articleReducer.ts | 2 +- src/article/useCases/fetchArticleUseCase.ts | 13 ------------- src/article/useCases/getArticleUseCase.ts | 14 -------------- 4 files changed, 11 insertions(+), 37 deletions(-) diff --git a/src/article/controller/articleViewModel.ts b/src/article/controller/articleViewModel.ts index c7340c6..d6d1428 100755 --- a/src/article/controller/articleViewModel.ts +++ b/src/article/controller/articleViewModel.ts @@ -1,6 +1,5 @@ import type { ArticleStore } from "../domain/articleStore"; import { useCallback, useEffect } from "react"; -import { Article } from "article/domain/articleEntity"; import { GetArticleUseCase } from "article/useCases/getArticleUseCase"; import { FetchArticleUseCase } from "article/useCases/fetchArticleUseCase"; import { useParams } from "react-router-dom"; @@ -12,15 +11,17 @@ function useArticleViewModel( ) { const { id } = useParams(); - const getArticle = useCallback( - () => { - getArticleUseCase.call(id ?? '').catch((_) => fetchArticleUseCase.call(id ?? '')); - console.log(id); - }, - [id] - ); + // const getArticle = useCallback( + // () => { + // getArticleUseCase.call(id ?? '').catch((_) => fetchArticleUseCase.call(id ?? '')); + // console.log(id); + // }, + // [id] + // ); - useEffect(getArticle, []); + useEffect(() => { + getArticleUseCase.call(id ?? '').catch((_) => fetchArticleUseCase.call(id ?? '')); + }, []); return { article: store.currentArticle, diff --git a/src/article/data/articleReducer.ts b/src/article/data/articleReducer.ts index b5ac846..7ea177d 100755 --- a/src/article/data/articleReducer.ts +++ b/src/article/data/articleReducer.ts @@ -17,7 +17,7 @@ const articleReducer = ( ): ArticleStoreState => { switch (action.type) { case actionTypes.SET_ARTICLE: - return { ...state, articles: action.updatedList, currentArticle: action.article }; + return { ...state, articles: action.updatedList, currentArticle: action.article, hasError: typeof action.article === undefined }; case actionTypes.GET_ARTICLE: return { ...state, isLoading: true }; case actionTypes.GET_ARTICLE_SUCCESS: diff --git a/src/article/useCases/fetchArticleUseCase.ts b/src/article/useCases/fetchArticleUseCase.ts index 30fb48f..1ab8f64 100644 --- a/src/article/useCases/fetchArticleUseCase.ts +++ b/src/article/useCases/fetchArticleUseCase.ts @@ -26,17 +26,4 @@ class FetchArticleUseCase { /* -------------------------------------------------------------------------- */ } - -const fetchArticleUseCase = async ( - fetchArticleCallback: (id: string) => Promise
, - setArticle: ArticleStore["setArticle"], - id: string, -): Promise
=> { - const article = await fetchArticleCallback(id); - if (article) { - await setArticle(article); - } - return article; -}; export { FetchArticleUseCase }; -export { fetchArticleUseCase }; diff --git a/src/article/useCases/getArticleUseCase.ts b/src/article/useCases/getArticleUseCase.ts index e4d560e..68894d6 100755 --- a/src/article/useCases/getArticleUseCase.ts +++ b/src/article/useCases/getArticleUseCase.ts @@ -22,18 +22,4 @@ class GetArticleUseCase { /* -------------------------------------------------------------------------- */ } - -const getArticleUseCase = async ( - getArticle: ArticleStore["getArticle"], - setArticle: ArticleStore["setArticle"], - id: Article["id"] -): Promise
=> { - const article = await getArticle(id); - if (article) { - await setArticle(article); - } - return article; -}; - export { GetArticleUseCase }; -export { getArticleUseCase }; -- 2.39.5 From d755be1f9547cdcbe454f4789b062f47b0d5da28 Mon Sep 17 00:00:00 2001 From: danysmall Date: Sun, 13 Nov 2022 01:16:11 +0300 Subject: [PATCH 3/8] Updated view model for article --- src/article/controller/articleViewModel.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/article/controller/articleViewModel.ts b/src/article/controller/articleViewModel.ts index d6d1428..0aa580c 100755 --- a/src/article/controller/articleViewModel.ts +++ b/src/article/controller/articleViewModel.ts @@ -11,17 +11,16 @@ function useArticleViewModel( ) { const { id } = useParams(); - // const getArticle = useCallback( - // () => { - // getArticleUseCase.call(id ?? '').catch((_) => fetchArticleUseCase.call(id ?? '')); - // console.log(id); - // }, - // [id] - // ); + const getArticle = useCallback( + (articleID: string) => { + getArticleUseCase.call(articleID).catch((_) => fetchArticleUseCase.call(articleID)); + }, + [id] + ); useEffect(() => { - getArticleUseCase.call(id ?? '').catch((_) => fetchArticleUseCase.call(id ?? '')); - }, []); + getArticle(id ?? ''); + }, [id]); return { article: store.currentArticle, -- 2.39.5 From 962f8cb1457514744be8a46171a9c8bdc84a3f3d Mon Sep 17 00:00:00 2001 From: danysmall Date: Sun, 13 Nov 2022 01:18:28 +0300 Subject: [PATCH 4/8] Restored original develop Dockerfile --- Dockerfile | 47 +++++++++++++++++------------------------------ 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/Dockerfile b/Dockerfile index ec52764..9a98d4f 100755 --- a/Dockerfile +++ b/Dockerfile @@ -1,43 +1,30 @@ -# Install dependencies of project -FROM node:fermium-alpine AS dependencies +# Install dependencies only when needed +FROM node:fermium-alpine AS deps +# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. RUN apk add --no-cache libc6-compat WORKDIR /home/app/ -COPY package.json ./ -RUN npm i +COPY package.json package-lock.json ./ +RUN npm ci -# Build application to bunch of static files -FROM node:fermium-alpine AS builder -WORKDIR /home/app/ -COPY --from=dependencies ./home/app/node_modules ./node_modules -COPY . . -RUN npm run build +# Bundle static assets with nginx +FROM node:fermium-alpine as production - -# NGINX image -FROM nginx:1.21.6 as production # Copy built assets from builder -COPY --from=builder /home/app/build /usr/share/nginx/html -# Add nginx.config -COPY nginx.conf /etc/nginx/conf.d/default.conf -# Copy setup nginx entrypoint file -COPY ./scripts/entrypoint.sh . +WORKDIR /home/app/ +COPY --from=deps ./home/app/node_modules ./node_modules +COPY . . # Expose ports -EXPOSE 80 - -# Execute script -RUN chmod +x ./entrypoint.sh +EXPOSE 3000 ENV NODE_ENV production -# ENV USER_NAME=node_user USER_UID=2000 GROUP_NAME=node_group GROUP_UID=2000 +ENV USER_NAME=node_user USER_UID=2000 GROUP_NAME=node_group GROUP_UID=2000 -# RUN deluser --remove-home node \ -# && addgroup --g ${GROUP_UID} -S ${GROUP_NAME} \ -# && adduser -D -S -s /sbin/nologin -u ${USER_UID} -G ${GROUP_NAME} ${USER_NAME} -# USER "${USER_NAME}" +RUN deluser --remove-home node \ + && addgroup --g ${GROUP_UID} -S ${GROUP_NAME} \ + && adduser -D -S -s /sbin/nologin -u ${USER_UID} -G ${GROUP_NAME} ${USER_NAME} +USER "${USER_NAME}" -ENTRYPOINT ["./entrypoint.sh"] -# Start serving -CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file +ENTRYPOINT [ "npm","start"] \ No newline at end of file -- 2.39.5 From 4312533d5cb1c3486b68f4f1a7ed8a5aba2626bb Mon Sep 17 00:00:00 2001 From: danysmall Date: Sun, 13 Nov 2022 01:19:40 +0300 Subject: [PATCH 5/8] Removed deprecated "setup" command from make container command --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 4384528..6170357 100755 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ dev: setup test: setup npm run test -container: setup +container: docker build -t ${PROJECT_NAME} . clean: -- 2.39.5 From be4587f7cc1d053db2393f64974a27906c371c95 Mon Sep 17 00:00:00 2001 From: danysmall Date: Sun, 13 Nov 2022 01:53:51 +0300 Subject: [PATCH 6/8] Added loading state for getting data from redux store --- src/article/data/articleCommonStateStore.ts | 2 +- src/article/data/articleReducer.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/article/data/articleCommonStateStore.ts b/src/article/data/articleCommonStateStore.ts index fdf3cbd..0081a0d 100755 --- a/src/article/data/articleCommonStateStore.ts +++ b/src/article/data/articleCommonStateStore.ts @@ -20,8 +20,8 @@ const useArticleCommonStore = (): ArticleStore => { setError(true); return null; } - setLoading(false); setCurrentArticle(fromStore); + setLoading(false); return fromStore; } setLoading(false); diff --git a/src/article/data/articleReducer.ts b/src/article/data/articleReducer.ts index 7ea177d..5aa2b62 100755 --- a/src/article/data/articleReducer.ts +++ b/src/article/data/articleReducer.ts @@ -17,13 +17,13 @@ const articleReducer = ( ): ArticleStoreState => { switch (action.type) { case actionTypes.SET_ARTICLE: - return { ...state, articles: action.updatedList, currentArticle: action.article, hasError: typeof action.article === undefined }; + return { ...state, articles: action.updatedList, currentArticle: action.article, hasError: typeof action.article === undefined, isLoading: false }; case actionTypes.GET_ARTICLE: return { ...state, isLoading: true }; case actionTypes.GET_ARTICLE_SUCCESS: return { ...state, isLoading: false, currentArticle: action.payload }; case actionTypes.GET_ARTICLE_FAILURE: - return { ...state, hasError: true, isLoading: false }; + return { ...state, hasError: false, isLoading: true }; default: return state; } -- 2.39.5 From 91a850563e61a5c4c2cb2d3c5a26f30d410b9ff4 Mon Sep 17 00:00:00 2001 From: danysmall Date: Sun, 13 Nov 2022 02:17:34 +0300 Subject: [PATCH 7/8] Build with no warnings --- src/article/controller/articleViewModel.ts | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/article/controller/articleViewModel.ts b/src/article/controller/articleViewModel.ts index 0aa580c..3900868 100755 --- a/src/article/controller/articleViewModel.ts +++ b/src/article/controller/articleViewModel.ts @@ -12,15 +12,27 @@ function useArticleViewModel( const { id } = useParams(); const getArticle = useCallback( - (articleID: string) => { + ( + articleID: string, + getArticleUseCase: GetArticleUseCase, + fetchArticleUseCase: FetchArticleUseCase + ) => { getArticleUseCase.call(articleID).catch((_) => fetchArticleUseCase.call(articleID)); }, - [id] + [] ); - useEffect(() => { - getArticle(id ?? ''); - }, [id]); + useEffect( + () => { + getArticle(id ?? '', getArticleUseCase, fetchArticleUseCase); + }, + [ + id, + getArticle, + getArticleUseCase, + fetchArticleUseCase + ], + ); return { article: store.currentArticle, -- 2.39.5 From 43bc96bdf62847082137be732beac56fcb6b2899 Mon Sep 17 00:00:00 2001 From: danysmall Date: Mon, 14 Nov 2022 12:31:42 +0300 Subject: [PATCH 8/8] Removed double request --- src/index.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/index.tsx b/src/index.tsx index 9f61062..0e8c2e4 100755 --- a/src/index.tsx +++ b/src/index.tsx @@ -25,7 +25,6 @@ if (!rootElement) throw new Error("Failed to find the root element"); const root = ReactDOM.createRoot(rootElement); root.render( - } /> @@ -46,7 +45,6 @@ root.render( }> - ); -- 2.39.5