diff --git a/.env.example b/.env.example index 915afad..36ba5f7 100644 --- a/.env.example +++ b/.env.example @@ -2,9 +2,12 @@ # development setup # ##################### +# These are needed for docker-compose +# Thus we need to specify --env-file .env.development when calling docker-compose REACT_PORT=3000 # local port for frontend -NODE_PORT=3001 # local port for backend +FLASK_RUN_PORT=3001 # local port for backend SERVER_NAME=yourdomainename.com # your domain name, for the nginx configuration +MODE=dev|prod # you must chose ! ################# # backend setup # @@ -15,7 +18,7 @@ DB_NAME=XXX DB_PORT=XXX # cors -CLIENT_ORIGIN=http://localhost:$REACT_PORT # URL of the frontend, to make a cors exception +CLIENT_ORIGIN=http://:$REACT_PORT # URL of the frontend, to make a cors exception # bcrypt SALT_ROUND=10 @@ -28,3 +31,13 @@ JWT_EXPIRATION="3d" MAX_TITLE=200 MAX_SUMMARY=200 MAX_BODY=10000 + +################## +# frontend setup # +################## +# REACT will ignore variables that don't start with REACT_APP_ except NODE_ENV +# Thus, all the backend secrets are protected even if we load this file in the frontend app + +NODE_ENV=development +REACT_APP_FILE_SIZE_MAX=40000 +REACT_APP_API_URL="http://:$FLASK_RUN_PORT" diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..6f09a2f --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,15 @@ + diff --git a/.github/workflows/prettier.yml b/.github/workflows/prettier.yml index 6012a39..a462d51 100644 --- a/.github/workflows/prettier.yml +++ b/.github/workflows/prettier.yml @@ -19,4 +19,4 @@ jobs: uses: creyD/prettier_action@v4.3 with: # This part is also where you can pass other options, for example: - prettier_options: --check **/*.{js,md,yml,css,html} + prettier_options: --check **/*.{js,md,yml,css,html} !mongodb/** diff --git a/.gitignore b/.gitignore index 9b0ff42..7452462 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ certbot node_modules package-lock.json team.txt +mongodb/** diff --git a/README.md b/README.md index 1872fbc..165f10d 100644 --- a/README.md +++ b/README.md @@ -48,29 +48,79 @@ You should be able to delete any article **you created** from the database. ## Deployment -You need to set up the frontend and backend applications to test the server. +You need to setup 3 things to run the website: + +- `.env.*` file +- MongoDB host folder +- start frontend, backend and database + For deployment, `development` and `production` modes are available > ##### Notes for the iScsc members: > -> Send me a message and I'll send you back a crypted version of the official `.env.production` and `.env.development` files. +> Send me a message and I'll send you back an encrypted version of the official `.env.production` and `.env.development` files. Here is a quick guide after cloning the repository: +### Setup the local MongoDB folder + +To make the database persistent through containers starting and stopping the database folder is shared with the host using a `docker` volume, you can see it in the [docker compose files](./docker-compose.yml). + +> :warning::warning: **IMPORTANT**: the following script will give rwx permissions on the DB folder to the UID 1001 due to bitnami/mongodb image [constraint](https://hub.docker.com/r/bitnami/mongodb) (the _Note_ under "Persisting your database"), if, on your systemn, it already exists and shouldn't have these access please consider modifying the image! + +However because the bitnami/mongodb container is a non-root container we've got to setup the right permission on that folder. +To set it up just run + +```bash +./scripts/setup-db-folder.sh +``` + ### Development mode -#### .env file +You have two choices to run the development mode: + +- with [`docker`](#docker) +- [manually](#manually-on-host) start the backend, frontend and setup a DB + +#### .env.development file Before deploying the application, you need to set the environment variables From the root directory of the repository, do the following: -``` +```bash cp .env.example .env.development ``` After copying the example config of `.env`, you must fill in the missing information in this file. Check the example for more information. -#### Backend +#### Docker + +Once your `.env.development` is [ready](#envdevelopment-file), run + +```bash +docker-compose --env-file .env.development --file docker-compose-dev.yml up -d --build +``` + +> Make sure the `docker` daemon is running, or start it with `systemctl start docker` + +The website is now up on `$CLIENT_URL` (specified in the `.env.development` file) + +To see the running application, and check the logs use + +```bash +docker ps +docker logs +``` + +Finally, you can stop it with + +```bash +docker-compose --env-file .env.development --file docker-compose-dev.yml down +``` + +#### Manually on host + +##### Backend From the root directory of the repository, do the following: @@ -82,7 +132,7 @@ npm run dev > You will need `nodemon` to run the backend. Use `npm install -g nodemon` to install it. Make sure you're supporting at least 2.0.20 with `nodemon --version`. Nodemon has been tested working fine with node 19. -#### Frontend +##### Frontend From the root directory of the repository, do the following: @@ -94,6 +144,10 @@ npm run start Make sure your're using at least version 8.19.2 by checking `npm --version`, and update if needed with `npm update`. +##### Database + +Start a MongoDB either in a container and expose a port or directly on your host with the right port configured. Then setup properly the .env, it should work but this is untested. + ### Production mode The production mode allows to deploy the application on the server. To use it, you will need: @@ -101,7 +155,7 @@ The production mode allows to deploy the application on the server. To use it, y - `docker` - `docker-compose` -#### .env file +#### .env.production file Before deploying the application, you need to set the environment variables, as for `developement` mode. @@ -159,7 +213,7 @@ Once everything is ready, run sudo docker-compose --env-file .env.production up -d --build ``` -> Make sure the `docker` daemon is running, or start it with `sudo dockerd` +> Make sure the `docker` daemon is running, or start it with `systemctl start docker` You application can now be started on `$CLIENT_URL` (specified in the `.env.production` file) diff --git a/backend/.gitignore b/backend/.gitignore index 935306f..8a128e4 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,3 +1,2 @@ -build -node_modules -.env \ No newline at end of file +__pycache__/** + diff --git a/backend/Dockerfile b/backend/Dockerfile index 3eaeb9d..2dbaa2a 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,7 +1,8 @@ -FROM node:18 +FROM python:3.10.9 WORKDIR /backend -COPY package.json . -RUN npm install + COPY . . -CMD node app.js +RUN pip install -r requirements.txt + +CMD flask run diff --git a/backend/Dockerfile.dev b/backend/Dockerfile.dev new file mode 100644 index 0000000..263105a --- /dev/null +++ b/backend/Dockerfile.dev @@ -0,0 +1,8 @@ +FROM python:3.10.9 + +WORKDIR /backend + +COPY requirements.txt . +RUN pip install -r requirements.txt + +CMD [ "python3", "-m" , "flask", "run", "--host=0.0.0.0"] \ No newline at end of file diff --git a/backend/app.js b/backend/app.js index a188618..80a52bc 100644 --- a/backend/app.js +++ b/backend/app.js @@ -8,7 +8,7 @@ const articleRoutes = require('./routes/articles') const httpServer = createServer(app) require('dotenv').config({ path: `../.env.${process.env.NODE_ENV}` }) -const { DB_PORT, DB_NAME, NODE_PORT, CLIENT_ORIGIN } = process.env +const { DB_PORT, DB_NAME, FLASK_RUN_PORT, CLIENT_ORIGIN } = process.env app.use(cors({ origin: CLIENT_ORIGIN || 'http://localhost:3000' })) app.use(express.json({ limit: '1MB' })) @@ -23,8 +23,8 @@ uri = `mongodb://mongodb:${DB_PORT}/${DB_NAME}?retryWrites=true&w=majority` mongoose .connect(uri) .then(() => { - httpServer.listen(NODE_PORT || 3001, () => { - console.log(`Server listening: http://localhost:${NODE_PORT}`) + httpServer.listen(FLASK_RUN_PORT || 3001, () => { + console.log(`Server listening: http://localhost:${FLASK_RUN_PORT}`) }) }) .catch(err => { diff --git a/backend/app.py b/backend/app.py new file mode 100644 index 0000000..81845de --- /dev/null +++ b/backend/app.py @@ -0,0 +1,14 @@ +from flask import Flask +from routes.articles import articles +from routes.users import users + +app = Flask(__name__) + + +@app.route("/") +def get_main(): + return "iscsc.fr backend is running" + + +app.register_blueprint(articles, url_prefix="/api/articles") +app.register_blueprint(users, url_prefix="/api/users") diff --git a/backend/controllers/articleController.py b/backend/controllers/articleController.py new file mode 100644 index 0000000..0f049ed --- /dev/null +++ b/backend/controllers/articleController.py @@ -0,0 +1,42 @@ +from flask import jsonify +from .dummy_data import * + +# TODO: Implement auth middleware logic to check if user is logged in + + +# TODO: fetch one article from database +def get_article(id): + if id == "1": + return jsonify(dummy_article), 200 + elif id == "2": + return jsonify(dummy_article_2), 200 + else: + return "", 404 + + +# TODO: create one article +def post_article(): + return jsonify({"success": True}), 200 + + +# TODO: modify one article in the database +def put_article(): + return jsonify({"success": True}), 200 + + +# TODO: delete one article from database +def delete_article(id): + return jsonify({"success": True}), 200 + + +# TODO: fetch all articles from database +def get_all_articles(): + return jsonify([dummy_article, dummy_article_2]), 200 + + +# TODO: fetch one article from database by author +def get_article_by_author(author): + if author == "ctmbl": + return jsonify([dummy_article, dummy_article_2]), 200 + else: + return "", 404 diff --git a/backend/controllers/dummy_data.py b/backend/controllers/dummy_data.py new file mode 100644 index 0000000..92bbc2a --- /dev/null +++ b/backend/controllers/dummy_data.py @@ -0,0 +1,20 @@ +dummy_article = { + "author": "ctmbl", + "title": "My dummy article", + "body": "This is a dummy article hardcoded to build the backend", + "_id": "1", +} + +dummy_article_2 = { + "author": "ctmbl", + "title": "My dummy article 2", + "body": "This is a dummy article 2", + "_id": "2", +} + +dummy_user = { + "name": "atxr", + "email": "atxr@iscsc.fr", + "password": "123456", + "_id": "1", +} diff --git a/backend/controllers/userController.py b/backend/controllers/userController.py new file mode 100644 index 0000000..02b7f81 --- /dev/null +++ b/backend/controllers/userController.py @@ -0,0 +1,20 @@ +from flask import jsonify +from flask import request + + +# TODO: log existing user +def login(): + if ( + "name" in request.form + and "password" in request.form + and request.form["name"] == "atxr" + and request.form["password"] == "123456" + ): + return jsonify({"success": request.form["name"] == "atxr"}), 200 + else: + return jsonify({"success": False}), 401 + + +# TODO: create new user +def signup(): + return jsonify({"success": True}), 200 diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..c05d987 --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1 @@ +flask==2.2.3 diff --git a/backend/routes/articles.py b/backend/routes/articles.py new file mode 100644 index 0000000..5f8fabe --- /dev/null +++ b/backend/routes/articles.py @@ -0,0 +1,18 @@ +from flask import Blueprint +from controllers.articleController import * + +articles = Blueprint("articles", "backend") # FIXME: don't hardcode backend + +articles.add_url_rule("/", "get_all_articles", get_all_articles, methods=["GET"]) +articles.add_url_rule("/", "get_article", get_article, methods=["GET"]) +articles.add_url_rule("/", "post_article", post_article, methods=["POST"]) +articles.add_url_rule("/", "delete_article", delete_article, methods=["DELETE"]) +articles.add_url_rule( + "/by-author/", + "get_article_by_author", + get_article_by_author, + methods=["GET"], +) + +# Not implemented yet in the frontend +# articles.add_url_rule(put_article, "/", methods=["PUT"]) diff --git a/backend/routes/users.py b/backend/routes/users.py new file mode 100644 index 0000000..770f150 --- /dev/null +++ b/backend/routes/users.py @@ -0,0 +1,11 @@ +from flask import Blueprint +from controllers.userController import * + +users = Blueprint("users", "backend") # FIXME: don't hardcode backend + +users.add_url_rule("/login", "login", login, methods=["POST"]) +users.add_url_rule("/signup", "signup", signup, methods=["POST"]) + +# Not implemented yet in the frontend +# users.add_url_rule(get_user, "/", methods=["GET"]) +# users.add_url_rule(put_user, "/", methods=["PUT"]) diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml new file mode 100644 index 0000000..a58f743 --- /dev/null +++ b/docker-compose-dev.yml @@ -0,0 +1,69 @@ +version: "3.8" + +services: + mongodb: + image: docker.io/bitnami/mongodb:latest + networks: + - database + env_file: ./.env.development + environment: + - "MONGODB_PORT_NUMBER=$DB_PORT" + ports: + - $DB_PORT:$DB_PORT + volumes: + - "./mongodb/dev:/bitnami/mongodb:rw" + + flask-app-dev: + depends_on: + - mongodb + build: + context: ./backend + dockerfile: Dockerfile.dev + networks: + - database + - proxy + env_file: ./.env.development + ports: + - $FLASK_RUN_PORT:$FLASK_RUN_PORT + volumes: + - ./backend/app.py:/backend/app.py:ro + - ./backend/controllers:/backend/controllers:ro + - ./backend/middleware:/backend/middleware:ro + - ./backend/models:/backend/models:ro + - ./backend/routes:/backend/routes:ro + + react-app-dev: + depends_on: + - flask-app-dev + build: + context: ./frontend + dockerfile: Dockerfile.dev + networks: + - proxy + env_file: ./.env.development + ports: + - $REACT_PORT:$REACT_PORT + volumes: + - "./frontend/public:/frontend/public:ro" + - "./frontend/src:/frontend/src:ro" + + nginx: + depends_on: + - react-app-dev + - flask-app-dev + - mongodb + build: + context: ./nginx + args: + - FLASK_RUN_PORT=$FLASK_RUN_PORT + - REACT_PORT=$REACT_PORT + - SERVER_NAME=$SERVER_NAME + - MODE=$MODE + networks: + - proxy + ports: + - 80:80 + +networks: + proxy: + database: diff --git a/docker-compose.yml b/docker-compose.yml index d7a5c00..e35f906 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,53 +2,55 @@ version: "3.8" services: mongodb: - image: docker.io/bitnami/mongodb:4.4 + image: docker.io/bitnami/mongodb:latest networks: - database restart: always env_file: ./.env.production + environment: + - "MONGODB_PORT_NUMBER=$DB_PORT" ports: - $DB_PORT:$DB_PORT volumes: - - "./mongodb:/data/db" + - "./mongodb/prod:/bitnami/mongodb" - node-app: + flask-app: depends_on: - mongodb + build: ./backend networks: - proxy - database - build: ./backend restart: unless-stopped env_file: ./.env.production ports: - - $NODE_PORT:$NODE_PORT + - $FLASK_RUN_PORT:$FLASK_RUN_PORT react-app: depends_on: - - node-app + - flask-app + build: + context: ./frontend networks: - proxy env_file: ./.env.production - build: - context: ./frontend ports: - $REACT_PORT:$REACT_PORT nginx: - restart: always - networks: - - proxy depends_on: - react-app - - node-app + - flask-app - mongodb build: context: ./nginx args: - - NODE_PORT=$NODE_PORT + - FLASK_RUN_PORT=$FLASK_RUN_PORT - REACT_PORT=$REACT_PORT - SERVER_NAME=$SERVER_NAME + networks: + - proxy + restart: always ports: - 80:80 - 443:443 diff --git a/frontend/Dockerfile.dev b/frontend/Dockerfile.dev new file mode 100644 index 0000000..d9a2633 --- /dev/null +++ b/frontend/Dockerfile.dev @@ -0,0 +1,6 @@ +FROM node:18 + +WORKDIR /frontend +COPY package.json . +RUN npm install +CMD npm run start \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e2a2352..24f1c89 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -12,6 +12,7 @@ "@testing-library/react": "^13.3.0", "@testing-library/user-event": "^13.5.0", "env-cmd": "^10.1.0", + "http-proxy-middleware": "^2.0.6", "react": "^18.2.0", "react-dom": "^18.2.0", "react-markdown": "^8.0.3", @@ -3941,7 +3942,7 @@ "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", - "dev": true, + "devOptional": true, "dependencies": { "@types/connect": "*", "@types/node": "*" @@ -3960,7 +3961,7 @@ "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "dev": true, + "devOptional": true, "dependencies": { "@types/node": "*" } @@ -4013,7 +4014,7 @@ "version": "4.17.15", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.15.tgz", "integrity": "sha512-Yv0k4bXGOH+8a+7bELd2PqHQsuiANB+A8a4gnQrkRWzrkKlb6KHaVvyXhqs04sVW/OWlbPyYxRgYlIXLfrufMQ==", - "dev": true, + "devOptional": true, "dependencies": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.31", @@ -4025,7 +4026,7 @@ "version": "4.17.31", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz", "integrity": "sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q==", - "dev": true, + "devOptional": true, "dependencies": { "@types/node": "*", "@types/qs": "*", @@ -4059,7 +4060,6 @@ "version": "1.17.9", "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", "integrity": "sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==", - "dev": true, "dependencies": { "@types/node": "*" } @@ -4147,7 +4147,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", - "dev": true + "devOptional": true }, "node_modules/@types/ms": { "version": "0.7.31", @@ -4186,13 +4186,13 @@ "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", - "dev": true + "devOptional": true }, "node_modules/@types/range-parser": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", - "dev": true + "devOptional": true }, "node_modules/@types/react": { "version": "18.0.25", @@ -4251,7 +4251,7 @@ "version": "1.15.0", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", - "dev": true, + "devOptional": true, "dependencies": { "@types/mime": "*", "@types/node": "*" @@ -8112,8 +8112,7 @@ "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" }, "node_modules/events": { "version": "3.3.0", @@ -8486,7 +8485,6 @@ "version": "1.15.2", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", - "dev": true, "funding": [ { "type": "individual", @@ -9217,7 +9215,6 @@ "version": "1.18.1", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dev": true, "dependencies": { "eventemitter3": "^4.0.0", "follow-redirects": "^1.0.0", @@ -9245,7 +9242,6 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", - "dev": true, "dependencies": { "@types/http-proxy": "^1.17.8", "http-proxy": "^1.18.1", @@ -9269,7 +9265,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", - "dev": true, "engines": { "node": ">=10" }, @@ -9608,7 +9603,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -9635,7 +9629,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "dependencies": { "is-extglob": "^2.1.1" }, @@ -16071,8 +16064,7 @@ "node_modules/requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, "node_modules/resolve": { "version": "1.22.1", @@ -21829,7 +21821,7 @@ "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", - "dev": true, + "devOptional": true, "requires": { "@types/connect": "*", "@types/node": "*" @@ -21848,7 +21840,7 @@ "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "dev": true, + "devOptional": true, "requires": { "@types/node": "*" } @@ -21901,7 +21893,7 @@ "version": "4.17.15", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.15.tgz", "integrity": "sha512-Yv0k4bXGOH+8a+7bELd2PqHQsuiANB+A8a4gnQrkRWzrkKlb6KHaVvyXhqs04sVW/OWlbPyYxRgYlIXLfrufMQ==", - "dev": true, + "devOptional": true, "requires": { "@types/body-parser": "*", "@types/express-serve-static-core": "^4.17.31", @@ -21913,7 +21905,7 @@ "version": "4.17.31", "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.31.tgz", "integrity": "sha512-DxMhY+NAsTwMMFHBTtJFNp5qiHKJ7TeqOo23zVEM9alT1Ml27Q3xcTH0xwxn7Q0BbMcVEJOs/7aQtUWupUQN3Q==", - "dev": true, + "devOptional": true, "requires": { "@types/node": "*", "@types/qs": "*", @@ -21947,7 +21939,6 @@ "version": "1.17.9", "resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.9.tgz", "integrity": "sha512-QsbSjA/fSk7xB+UXlCT3wHBy5ai9wOcNDWwZAtud+jXhwOM3l+EYZh8Lng4+/6n8uar0J7xILzqftJdJ/Wdfkw==", - "dev": true, "requires": { "@types/node": "*" } @@ -22028,7 +22019,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/@types/mime/-/mime-3.0.1.tgz", "integrity": "sha512-Y4XFY5VJAuw0FgAqPNd6NNoV44jbq9Bz2L7Rh/J6jLTiHBSBJa9fxqQIvkIld4GsoDOcCbvzOUAbLPsSKKg+uA==", - "dev": true + "devOptional": true }, "@types/ms": { "version": "0.7.31", @@ -22067,13 +22058,13 @@ "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", - "dev": true + "devOptional": true }, "@types/range-parser": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", - "dev": true + "devOptional": true }, "@types/react": { "version": "18.0.25", @@ -22132,7 +22123,7 @@ "version": "1.15.0", "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.0.tgz", "integrity": "sha512-z5xyF6uh8CbjAu9760KDKsH2FcDxZ2tFCsA4HIMWE6IkiYMXfVoa+4f9KX+FN0ZLsaMw1WNG2ETLA6N+/YA+cg==", - "dev": true, + "devOptional": true, "requires": { "@types/mime": "*", "@types/node": "*" @@ -25025,8 +25016,7 @@ "eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" }, "events": { "version": "3.3.0", @@ -25339,8 +25329,7 @@ "follow-redirects": { "version": "1.15.2", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", - "dev": true + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" }, "for-each": { "version": "0.3.3", @@ -25870,7 +25859,6 @@ "version": "1.18.1", "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", - "dev": true, "requires": { "eventemitter3": "^4.0.0", "follow-redirects": "^1.0.0", @@ -25892,7 +25880,6 @@ "version": "2.0.6", "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-2.0.6.tgz", "integrity": "sha512-ya/UeJ6HVBYxrgYotAZo1KvPWlgB48kUJLDePFeneHsVujFaW5WNj2NgWCAE//B1Dl02BIfYlpNgBy8Kf8Rjmw==", - "dev": true, "requires": { "@types/http-proxy": "^1.17.8", "http-proxy": "^1.18.1", @@ -25904,8 +25891,7 @@ "is-plain-obj": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-3.0.0.tgz", - "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==", - "dev": true + "integrity": "sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==" } } }, @@ -26128,8 +26114,7 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" }, "is-fullwidth-code-point": { "version": "3.0.0", @@ -26147,7 +26132,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, "requires": { "is-extglob": "^2.1.1" } @@ -30793,8 +30777,7 @@ "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", - "dev": true + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" }, "resolve": { "version": "1.22.1", diff --git a/frontend/package.json b/frontend/package.json index a08355b..4e42b72 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,5 +1,4 @@ { - "proxy": "http://localhost:3001", "name": "iscsc.fr-frontend", "version": "0.1.2", "description": "Frontend of the iscsc.fr website", @@ -9,6 +8,7 @@ "@testing-library/react": "^13.3.0", "@testing-library/user-event": "^13.5.0", "env-cmd": "^10.1.0", + "http-proxy-middleware": "^2.0.6", "react": "^18.2.0", "react-dom": "^18.2.0", "react-markdown": "^8.0.3", @@ -20,7 +20,7 @@ "react-scripts": "5.0.1" }, "scripts": { - "start": "env-cmd -f ../.env.development react-scripts start", + "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", diff --git a/frontend/src/setupProxy.js b/frontend/src/setupProxy.js new file mode 100644 index 0000000..960fc73 --- /dev/null +++ b/frontend/src/setupProxy.js @@ -0,0 +1,11 @@ +const { createProxyMiddleware } = require('http-proxy-middleware') + +module.exports = function (app) { + app.use( + '/api', + createProxyMiddleware({ + target: process.env.REACT_APP_API_URL, + changeOrigin: true + }) + ) +} diff --git a/nginx/Dockerfile b/nginx/Dockerfile index 108afc3..a06a573 100644 --- a/nginx/Dockerfile +++ b/nginx/Dockerfile @@ -1,7 +1,7 @@ FROM nginx:1.23.2 -ARG NODE_PORT -ENV NODE_PORT=$NODE_PORT +ARG FLASK_RUN_PORT +ENV FLASK_RUN_PORT=$FLASK_RUN_PORT ARG REACT_PORT ENV REACT_PORT=$REACT_PORT @@ -9,7 +9,10 @@ ENV REACT_PORT=$REACT_PORT ARG SERVER_NAME ENV SERVER_NAME=$SERVER_NAME +ARG MODE +ENV MODE=$MODE + WORKDIR /nginx COPY . . -CMD ./run_nginx.sh +CMD MODE=$MODE ./run_nginx.sh EXPOSE 80 diff --git a/nginx/nginx.conf.dev.template b/nginx/nginx.conf.dev.template new file mode 100644 index 0000000..8d9c393 --- /dev/null +++ b/nginx/nginx.conf.dev.template @@ -0,0 +1,41 @@ +worker_processes 1; + +# see https://www.digitalocean.com/community/tutorials/nginx-access-logs-error-logs#nginx-error-log-severity-levels for configuring +# error_log logs/error.log info; + +events { + worker_connections 1024; +} + +http { + include mime.types; + default_type application/octet-stream; + + # log_format main '${DOLLAR}remote_addr - ${DOLLAR}remote_user [${DOLLAR}time_local] "${DOLLAR}request" ' + # '${DOLLAR}status ${DOLLAR}body_bytes_sent "${DOLLAR}http_referer" ' + # '"${DOLLAR}http_user_agent" "${DOLLAR}http_x_forwarded_for"'; + # access_log logs/access.log main; + sendfile on; + + keepalive_timeout 65; + + server { + listen 80; + server_name 127.0.0.1; + + location / { + proxy_pass http://react-app-dev:$REACT_PORT; + } + + location /api { + proxy_pass http://flask-app-dev:$FLASK_RUN_PORT; + } + + # redirect server error pages to the static page /50x.html + # + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + } + } +} diff --git a/nginx/nginx.conf.template b/nginx/nginx.conf.prod.template similarity index 96% rename from nginx/nginx.conf.template rename to nginx/nginx.conf.prod.template index e532649..3ef392a 100644 --- a/nginx/nginx.conf.template +++ b/nginx/nginx.conf.prod.template @@ -57,7 +57,7 @@ http { } location /api { - proxy_pass http://node-app:$NODE_PORT; + proxy_pass http://flask-app:$FLASK_RUN_PORT; } # redirect server error pages to the static page /50x.html diff --git a/nginx/run_nginx.sh b/nginx/run_nginx.sh index 0dfe9fc..1c949f8 100755 --- a/nginx/run_nginx.sh +++ b/nginx/run_nginx.sh @@ -3,5 +3,12 @@ # To avoid injection, use ${DOLLAR}DONT_INJECT_ME # Start nginx when the config file is genereated export DOLLAR='$' -envsubst < nginx.conf.template > /etc/nginx/nginx.conf + +NGINX_CONF_TEMPLATE=nginx.conf.${MODE}.template +if [ ! -f "$NGINX_CONF_TEMPLATE" ]; then + echo "nginx template: '$NGINX_CONF_TEMPLATE' not found, exiting" + exit 1 +fi + +envsubst < "$NGINX_CONF_TEMPLATE" > /etc/nginx/nginx.conf nginx -g "daemon off;" \ No newline at end of file diff --git a/package.json b/package.json index 70b47de..ba32345 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "description": "The iScsc website", "private": true, "scripts": { - "version": "git add package.json" + "version": "git add package.json", + "pretty": "npx prettier --write '**/*.{js,md,yml,css,html}' '!mongodb/**'" } } diff --git a/scripts/setup-db-folder.sh b/scripts/setup-db-folder.sh new file mode 100755 index 0000000..cace967 --- /dev/null +++ b/scripts/setup-db-folder.sh @@ -0,0 +1,45 @@ +#!/bin/sh + +log_any () { + echo -e "[\e[$1m$2\e[0m] $3" +} + +log_error () { log_any "31" "-" "$1"; } +log_ok () { log_any "32" "+" "$1"; } +log_warning () { log_any "33" "~" "$1"; } +log_hint () { log_any "34" "?" "$1"; } +log_info () { log_any "35" "i" "$1"; } + +check_pwd () { + root="$(dirname $(realpath $(dirname "$0")))" + [ "$root" != "$(pwd)" ] && { + log_error "the script should be run from the root of the repo" + log_error "expected '$root'" + log_error "found '$(pwd)'" + exit 1 + } +} + +setup_folder () { + local folder="$1" + + mkdir -p $folder/dev + mkdir $folder/prod + chmod -R 774 $folder + log_info "sudo permission needed to chown newly created '$folder'" + sudo chown -R :root $folder +} + +main () { + check_pwd + + MONGODB_FOLDER="mongodb" + if [ ! -d "$MONGODB_FOLDER" ]; then + setup_folder "$MONGODB_FOLDER" + exit 0 + else + log_error "'$MONGODB_FOLDER' folder already exists, please deal with it manually" + fi +} + +main "$@" \ No newline at end of file