diff --git a/.env.example b/.env.example index f8209f46d40bb5947e16d98f126b6f2a634ed246..4b6ec2500455d9d90dbdb2eb2da08f22c69b6e80 100644 --- a/.env.example +++ b/.env.example @@ -9,5 +9,8 @@ DB_NAME=film-db # ADMINER ADMINER_PORT=8080 +# NGINX +NGINX_PORT=3050 + # CLIENT CLIENT_PORT=3000 diff --git a/.gitignore b/.gitignore index 6ee69d07071012e1d6c3dc09e75e27033a89bca0..4e9d991eebf180ab844f733929a2de14026601a0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,5 @@ -# Node -node_modules/ - # JetBrains IDEA .idea/ # .env files .env - -# Database -database-storage diff --git a/backend/.eslintrc.js b/backend/.eslintrc.js index 8f5aedb718c87c8f4a192c2530ad1f90efc8dc25..b9aada61a79eb6224475099eed969af5cbd835c4 100644 --- a/backend/.eslintrc.js +++ b/backend/.eslintrc.js @@ -17,9 +17,85 @@ module.exports = { }, ignorePatterns: ['.eslintrc.js'], rules: { - '@typescript-eslint/interface-name-prefix': 'off', - '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/explicit-module-boundary-types': 'off', - '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/explicit-function-return-type': ['error'], + '@typescript-eslint/no-explicit-any': ['error'], + '@typescript-eslint/prefer-nullish-coalescing': ['error'], + '@typescript-eslint/restrict-plus-operands': ['error'], + '@typescript-eslint/type-annotation-spacing': ['error'], + + '@typescript-eslint/brace-style': ['error', '1tbs'], + '@typescript-eslint/comma-spacing': ['error', { 'before': false, 'after': true }], + '@typescript-eslint/default-param-last': ['error'], + '@typescript-eslint/dot-notation': ['error'], + '@typescript-eslint/indent': ['error', 2], + '@typescript-eslint/keyword-spacing': ['error', { 'before': true, 'after': true }], + '@typescript-eslint/no-empty-function': ['error'], + '@typescript-eslint/no-extra-parens': ['error'], + '@typescript-eslint/no-extra-semi': ['error'], + '@typescript-eslint/no-shadow': ['error'], + '@typescript-eslint/no-unused-expressions': ['error'], + '@typescript-eslint/no-unused-vars': ['error'], + '@typescript-eslint/quotes': ['error', 'single'], + '@typescript-eslint/require-await': ['error'], + '@typescript-eslint/semi': ['error', 'always'], + '@typescript-eslint/space-before-blocks': ['error'], + + 'no-unreachable': 'error', + 'no-unused-vars': 'off', + + 'camelcase': 'error', + 'default-param-last': 'off', + 'dot-notation': 'off', + 'eqeqeq': 'error', + 'no-confusing-arrow': 'error', + 'no-else-return': 'error', + 'no-empty': 'error', + 'no-empty-function': 'off', + 'no-extra-semi': 'off', + 'no-lonely-if': 'error', + 'no-mixed-operators': 'error', + 'no-multi-assign': 'error', + 'no-negated-condition': 'error', + 'no-nested-ternary': 'error', + 'no-param-reassign': 'error', + 'no-shadow': 'off', + 'no-shadow-restricted-names': 'error', + 'no-throw-literal': 'error', + 'no-unneeded-ternary': 'error', + 'no-unused-expressions': 'off', + 'no-useless-rename': 'error', + 'no-useless-return': 'error', + 'no-var': 'error', + 'operator-assignment': ['error', 'always'], + 'prefer-const': 'error', + 'prefer-template': 'error', + 'require-await': 'off', + 'require-yield': 'error', + 'spaced-comment': ['error', 'always'], + + 'arrow-parens': ['error', 'always'], + 'arrow-spacing': 'error', + 'block-spacing': 'error', + 'brace-style': 'off', + 'comma-spacing': 'off', + 'comma-style': ['error', 'last'], + 'func-call-spacing': ['error', 'never'], + 'implicit-arrow-linebreak': ['error', 'beside'], + 'indent': 'off', + 'jsx-quotes': ['error', 'prefer-single'], + 'keyword-spacing': 'off', + 'no-mixed-spaces-and-tabs': 'error', + 'no-multi-spaces': 'error', + 'no-multiple-empty-lines': 'error', + 'no-tabs': 'error', + 'no-trailing-spaces': 'error', + 'no-whitespace-before-property': 'error', + 'quotes': 'off', + 'rest-spread-spacing': ['error', 'never'], + 'semi': 'off', + 'space-before-blocks': 'off', + 'space-in-parens': 'error', + 'switch-colon-spacing': 'error', + 'wrap-regex': 'error', }, }; diff --git a/backend/.gitignore b/backend/.gitignore index 22f55adc5647206db11558139164c3deb77f5c01..5bb3f5e76208dba8b65262ca659ef2519c9385ca 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,3 +1,6 @@ +# Database +database-storage + # compiled output /dist /node_modules diff --git a/backend/.prettierrc b/backend/.prettierrc index dcb72794f5300a3e0ccd2ad0669d802b62f3d370..920290445a33e01b1497022e6106dbd300585243 100644 --- a/backend/.prettierrc +++ b/backend/.prettierrc @@ -1,4 +1,5 @@ { "singleQuote": true, - "trailingComma": "all" + "trailingComma": "all", + "bracketSameLine": true } \ No newline at end of file diff --git a/backend/package-lock.json b/backend/package-lock.json index a85dbef5bd9029312a90321961a7dfa6adabed22..ca5a6ee8fbbf039a2b1660f247e2d90311f7c3da 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -10,6 +10,7 @@ "license": "UNLICENSED", "dependencies": { "@nestjs/common": "^8.0.0", + "@nestjs/config": "^2.1.0", "@nestjs/core": "^8.0.0", "@nestjs/platform-express": "^8.0.0", "jshint": "^2.13.4", @@ -1541,6 +1542,22 @@ } } }, + "node_modules/@nestjs/config": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-2.1.0.tgz", + "integrity": "sha512-wUpt1/QJEN7xnJl4pM3c9rHrY1widq2yPOZfjaMD1//XAP9LLHTaW+RxSEG6BSGIm3w4wGtjco+gKNB2WL7yRg==", + "dependencies": { + "dotenv": "16.0.1", + "dotenv-expand": "8.0.3", + "lodash": "4.17.21", + "uuid": "8.3.2" + }, + "peerDependencies": { + "@nestjs/common": "^7.0.0 || ^8.0.0", + "reflect-metadata": "^0.1.13", + "rxjs": "^6.0.0 || ^7.2.0" + } + }, "node_modules/@nestjs/core": { "version": "8.4.6", "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-8.4.6.tgz", @@ -3523,6 +3540,22 @@ "domelementtype": "1" } }, + "node_modules/dotenv": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz", + "integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/dotenv-expand": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-8.0.3.tgz", + "integrity": "sha512-SErOMvge0ZUyWd5B0NXMQlDkN+8r+HhVUsxgOO7IoPDOdDRD2JjExpN6y3KnFR66jsJMwSn1pqIivhU5rcJiNg==", + "engines": { + "node": ">=12" + } + }, "node_modules/ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", @@ -9979,6 +10012,17 @@ "uuid": "8.3.2" } }, + "@nestjs/config": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-2.1.0.tgz", + "integrity": "sha512-wUpt1/QJEN7xnJl4pM3c9rHrY1widq2yPOZfjaMD1//XAP9LLHTaW+RxSEG6BSGIm3w4wGtjco+gKNB2WL7yRg==", + "requires": { + "dotenv": "16.0.1", + "dotenv-expand": "8.0.3", + "lodash": "4.17.21", + "uuid": "8.3.2" + } + }, "@nestjs/core": { "version": "8.4.6", "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-8.4.6.tgz", @@ -11518,6 +11562,16 @@ "domelementtype": "1" } }, + "dotenv": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.1.tgz", + "integrity": "sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ==" + }, + "dotenv-expand": { + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-8.0.3.tgz", + "integrity": "sha512-SErOMvge0ZUyWd5B0NXMQlDkN+8r+HhVUsxgOO7IoPDOdDRD2JjExpN6y3KnFR66jsJMwSn1pqIivhU5rcJiNg==" + }, "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", diff --git a/backend/package.json b/backend/package.json index 8c71fb3e7ca4c36dbbe8e3a77716bd8be33803db..21cdb97e6e3e69b5129c275f8a811eb986e64a49 100644 --- a/backend/package.json +++ b/backend/package.json @@ -22,6 +22,7 @@ }, "dependencies": { "@nestjs/common": "^8.0.0", + "@nestjs/config": "^2.1.0", "@nestjs/core": "^8.0.0", "@nestjs/platform-express": "^8.0.0", "jshint": "^2.13.4", diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index 86628031ca2a10fe172fe824f69d1720c44b43ce..278b638f9d853fe8c1ff8cb67ea693775b49b99e 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -1,9 +1,10 @@ import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; import { AppController } from './app.controller'; import { AppService } from './app.service'; @Module({ - imports: [], + imports: [ConfigModule.forRoot()], controllers: [AppController], providers: [AppService], }) diff --git a/backend/src/main.ts b/backend/src/main.ts index 13cad38cff92aa3b3d3ef6232306e450cadf5713..7ec8d4b3d866f07bcd7d37cf38ec08df84363467 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -1,8 +1,14 @@ import { NestFactory } from '@nestjs/core'; +import { ConfigService } from '@nestjs/config'; import { AppModule } from './app.module'; -async function bootstrap() { +async function bootstrap(): Promise<void> { const app = await NestFactory.create(AppModule); - await app.listen(3000); + const configService = app.get(ConfigService); + + const port = configService.get<number>('PORT'); + console.log(`NestJS server is listening on: http://localhost:${port}`); + await app.listen(port); } + bootstrap(); diff --git a/docker-compose.yml b/docker-compose.yml index a3c568507ecbf46ba016da700befbefada105898..b7466238cdfb6adb3d288bf0f49a47912eaaaa94 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -8,16 +8,17 @@ services: context: ./backend dockerfile: Dockerfile command: npm run start:debug - env_file: .env ports: - - 127.0.0.1:${SERVER_PORT}:3000 + - 127.0.0.1:${SERVER_PORT}:${SERVER_PORT} + networks: + - backend-network volumes: # changes in host's ./backend directory will be propagated to the container - ./backend:/usr/src/app # this prevents our host files from overriding container's node_modules - exclude-server:/usr/src/app/node_modules/ - networks: - - backend-network + environment: + - PORT=${SERVER_PORT} restart: always depends_on: - postgres @@ -25,11 +26,10 @@ services: postgres: container_name: postgres image: postgres:14.3-alpine - env_file: .env - volumes: - - ./backend/database-storage:/var/lib/postgresql/data networks: - backend-network + volumes: + - ./backend/database-storage:/var/lib/postgresql/data environment: POSTGRES_USER: ${DB_USER} POSTGRES_PASSWORD: ${DB_PASSWORD} @@ -39,7 +39,6 @@ services: adminer: container_name: adminer image: adminer:4.8.1 - env_file: .env ports: - 127.0.0.1:${ADMINER_PORT}:${ADMINER_PORT} networks: @@ -50,26 +49,51 @@ services: depends_on: - postgres + nginx: + container_name: nginx + image: nginx:1.21.6-alpine + ports: + - 127.0.0.1:${NGINX_PORT}:${NGINX_PORT} + networks: + - backend-network + - frontend-network + volumes: + - ./nginx/templates:/etc/nginx/templates + - ./nginx/nginx.conf:/etc/nginx/nginx.conf + environment: + - NGINX_PORT=${NGINX_PORT} + - SERVER_PORT=${SERVER_PORT} + restart: always + depends_on: + - server + - postgres + client: container_name: client image: client:1.0.0 build: context: ./frontend dockerfile: Dockerfile - env_file: .env ports: - - 127.0.0.1:${CLIENT_PORT}:3000 + - 127.0.0.1:${CLIENT_PORT}:${CLIENT_PORT} + networks: + - frontend-network volumes: # changes in host's ./frontend directory will be propagated to the container - ./frontend:/usr/src/app # this prevents our host files from overriding container's node_modules - exclude-client:/usr/src/app/node_modules/ + environment: + - PORT=${CLIENT_PORT} + # all the traffic goes through Nginx server + - SERVER_PORT=${NGINX_PORT} restart: always +networks: + backend-network: + frontend-network: + volumes: database-storage: exclude-server: exclude-client: - -networks: - backend-network: diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 4ae3ca97e58ce0222fa411f31f51a099880126be..6f69c7ad2ac8e1307dc16752fd9a41674bf394b8 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -552,6 +552,11 @@ "node": ">= 8.0.0" } }, + "node_modules/@types/node": { + "version": "17.0.42", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.42.tgz", + "integrity": "sha512-Q5BPGyGKcvQgAMbsr7qEGN/kIPN6zZecYYABeTDBizOsau+2NMdSVTar9UQw21A2+JyA2KRNDYaYrPB0Rpk2oQ==" + }, "node_modules/@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", @@ -2651,6 +2656,11 @@ "picomatch": "^2.2.2" } }, + "@types/node": { + "version": "17.0.42", + "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.42.tgz", + "integrity": "sha512-Q5BPGyGKcvQgAMbsr7qEGN/kIPN6zZecYYABeTDBizOsau+2NMdSVTar9UQw21A2+JyA2KRNDYaYrPB0Rpk2oQ==" + }, "@types/prop-types": { "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 0b85ee5e9480eff7fc43e870e19373455966af98..7a04fd94acb28e053476320c97169b67b45d2b94 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,4 +1,4 @@ -import { defineConfig } from 'vite' +import { defineConfig, loadEnv } from 'vite' import react from '@vitejs/plugin-react' // https://vitejs.dev/config/ @@ -7,6 +7,7 @@ export default defineConfig({ server: { watch: { usePolling: true - } + }, + port: +process.env.PORT } }) diff --git a/nginx/nginx.conf b/nginx/nginx.conf new file mode 100644 index 0000000000000000000000000000000000000000..e39c4ff5b869d5eb4278e325c3b90e6c1e78bb1e --- /dev/null +++ b/nginx/nginx.conf @@ -0,0 +1,28 @@ +user nginx; +worker_processes auto; + +error_log /var/log/nginx/error.log; +pid /var/run/nginx.pid; + +events { + worker_connections 1024; +} + +http { + include /etc/nginx/mime.types; + default_type application/octet-stream; + + log_format main '$remote_addr - $remote_user [$time_local] "$request" ' + '$status $body_bytes_sent "$http_referer" ' + '"$http_user_agent" "$http_x_forwarded_for"'; + + # TODO: logging does not work + access_log /var/log/nginx/access.log; + + sendfile on; + + keepalive_timeout 65; + + # template files in /etc/nginx/templates/*.template are outputed as a result of executing envsubst to the /etc/nginx/conf.d folder + include /etc/nginx/conf.d/*.conf; +} diff --git a/nginx/templates/default.conf.template b/nginx/templates/default.conf.template new file mode 100644 index 0000000000000000000000000000000000000000..831ce45a67c5df1ce253e15555ad05d032767c40 --- /dev/null +++ b/nginx/templates/default.conf.template @@ -0,0 +1,26 @@ +upstream nestjs_server { + server server:${SERVER_PORT}; +} + +server { + # IPv4 + listen ${NGINX_PORT} default_server; + # IPv6 + listen [::]:${NGINX_PORT} default_server; + + location / { + proxy_http_version 1.1; + proxy_cache_bypass $http_upgrade; + + #proxy_buffering off; + + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + proxy_pass http://nestjs_server; + } +}