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/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/.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..89b1b1611b1528bb0d18dd6a58bbba49b3819987 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -1,9 +1,14 @@ 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({ + envFilePath: '../.env', + }), + ], controllers: [AppController], providers: [AppService], }) diff --git a/backend/src/main.ts b/backend/src/main.ts index 13cad38cff92aa3b3d3ef6232306e450cadf5713..655bf673ce3eddf4938fe6b461853f200630a9a6 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -1,8 +1,12 @@ 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 = new ConfigService(); + const port = configService.get('SERVER_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..23fb7268e12b60d7e6068ada5c40573cb1ae1141 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,7 +10,7 @@ services: command: npm run start:debug env_file: .env ports: - - 127.0.0.1:${SERVER_PORT}:3000 + - 127.0.0.1:${SERVER_PORT}:${SERVER_PORT} volumes: # changes in host's ./backend directory will be propagated to the container - ./backend:/usr/src/app @@ -50,6 +50,25 @@ services: depends_on: - postgres + nginx: + container_name: nginx + image: nginx:1.21.6-alpine + env_file: .env + ports: + - 127.0.0.1:${NGINX_PORT}:${NGINX_PORT} + volumes: + - ./nginx/templates:/etc/nginx/templates + - ./nginx/nginx.conf:/etc/nginx/nginx.conf + networks: + - backend-network + environment: + - NGINX_PORT=${NGINX_PORT} + - SERVER_PORT=${SERVER_PORT} + restart: always + depends_on: + - server + - postgres + client: container_name: client image: client:1.0.0 @@ -58,7 +77,7 @@ services: dockerfile: Dockerfile env_file: .env ports: - - 127.0.0.1:${CLIENT_PORT}:3000 + - 127.0.0.1:${CLIENT_PORT}:${CLIENT_PORT} volumes: # changes in host's ./frontend directory will be propagated to the container - ./frontend:/usr/src/app diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 7dd2bb0cb606862a8044b4015c16064166110c44..0747bbc18c58164ae373286a8a06917b5b92f180 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,6 +8,7 @@ "name": "frontend", "version": "0.0.0", "dependencies": { + "@types/node": "^17.0.42", "react": "^18.0.0", "react-dom": "^18.0.0" }, @@ -484,6 +485,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", @@ -1745,6 +1751,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/package.json b/frontend/package.json index fb6322e2217ffe07934a324a00c8ba60960d5a7a..c15fdb4f74699a1277e8eb5d177cbf27580a6707 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,6 +8,7 @@ "preview": "vite preview" }, "dependencies": { + "@types/node": "^17.0.42", "react": "^18.0.0", "react-dom": "^18.0.0" }, @@ -18,4 +19,4 @@ "typescript": "^4.6.3", "vite": "^2.9.9" } -} \ No newline at end of file +} diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts index 0b85ee5e9480eff7fc43e870e19373455966af98..0bceb31594a0513b2adff102b2ad05b655f1f160 100644 --- a/frontend/vite.config.ts +++ b/frontend/vite.config.ts @@ -1,12 +1,17 @@ -import { defineConfig } from 'vite' +import { defineConfig, loadEnv } from 'vite' import react from '@vitejs/plugin-react' // https://vitejs.dev/config/ -export default defineConfig({ - plugins: [react()], - server: { - watch: { - usePolling: true +export default ({ mode }) => { + process.env = {...process.env, ...loadEnv(mode, `../${process.cwd()}`, '')}; + + return defineConfig({ + plugins: [react()], + server: { + watch: { + usePolling: true + }, + port: +process.env.CLIENT_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; + } +}