diff --git a/backend/package-lock.json b/backend/package-lock.json index 378f515..d0dbbee 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -10,9 +10,74 @@ "license": "ISC", "dependencies": { "cors": "^2.8.5", - "express": "^5.2.1" + "express": "^5.2.1", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1" } }, + "node_modules/@apidevtools/json-schema-ref-parser": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@apidevtools/json-schema-ref-parser/-/json-schema-ref-parser-9.1.2.tgz", + "integrity": "sha512-r1w81DpR+KyRWd3f+rk6TNqMgedmAxZP5v5KWlXQWlgMUUtyEJch0DKEci1SorPMiSeM8XPl7MZ3miJ60JIpQg==", + "license": "MIT", + "dependencies": { + "@jsdevtools/ono": "^7.1.3", + "@types/json-schema": "^7.0.6", + "call-me-maybe": "^1.0.1", + "js-yaml": "^4.1.0" + } + }, + "node_modules/@apidevtools/openapi-schemas": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@apidevtools/openapi-schemas/-/openapi-schemas-2.1.0.tgz", + "integrity": "sha512-Zc1AlqrJlX3SlpupFGpiLi2EbteyP7fXmUOGup6/DnkRgjP9bgMM/ag+n91rsv0U1Gpz0H3VILA/o3bW7Ua6BQ==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/@apidevtools/swagger-methods": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-methods/-/swagger-methods-3.0.2.tgz", + "integrity": "sha512-QAkD5kK2b1WfjDS/UQn/qQkbwF31uqRjPTrsCs5ZG9BQGAkjwvqGFjjPqAuzac/IYzpPtRzjCP1WrTuAIjMrXg==", + "license": "MIT" + }, + "node_modules/@apidevtools/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/@apidevtools/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-sNiLY51vZOmSPFZA5TF35KZ2HbgYklQnTSDnkghamzLb3EkNtcQnrBQEj5AOCxHpTtXpqMCRM1CrmV2rG6nw4g==", + "license": "MIT", + "dependencies": { + "@apidevtools/json-schema-ref-parser": "^9.0.6", + "@apidevtools/openapi-schemas": "^2.0.4", + "@apidevtools/swagger-methods": "^3.0.2", + "@jsdevtools/ono": "^7.1.3", + "call-me-maybe": "^1.0.1", + "z-schema": "^5.0.1" + }, + "peerDependencies": { + "openapi-types": ">=7" + } + }, + "node_modules/@jsdevtools/ono": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@jsdevtools/ono/-/ono-7.1.3.tgz", + "integrity": "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg==", + "license": "MIT" + }, + "node_modules/@scarf/scarf": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz", + "integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==", + "hasInstallScript": true, + "license": "Apache-2.0" + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "license": "MIT" + }, "node_modules/accepts": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", @@ -26,6 +91,18 @@ "node": ">= 0.6" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, "node_modules/body-parser": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.1.tgz", @@ -50,6 +127,16 @@ "url": "https://opencollective.com/express" } }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -88,6 +175,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", + "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, "node_modules/content-disposition": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", @@ -167,6 +275,18 @@ "node": ">= 0.8" } }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -232,6 +352,15 @@ "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", "license": "MIT" }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -323,6 +452,12 @@ "node": ">= 0.8" } }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC" + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -369,6 +504,27 @@ "node": ">= 0.4" } }, + "node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -441,6 +597,17 @@ "url": "https://opencollective.com/express" } }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -462,6 +629,38 @@ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "deprecated": "This package is deprecated. Use the optional chaining (?.) operator instead.", + "license": "MIT" + }, + "node_modules/lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", + "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", + "license": "MIT" + }, + "node_modules/lodash.mergewith": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", + "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", + "license": "MIT" + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -517,6 +716,18 @@ "url": "https://opencollective.com/express" } }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -574,6 +785,13 @@ "wrappy": "1" } }, + "node_modules/openapi-types": { + "version": "12.1.3", + "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-12.1.3.tgz", + "integrity": "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==", + "license": "MIT", + "peer": true + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -583,6 +801,15 @@ "node": ">= 0.8" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-to-regexp": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", @@ -799,6 +1026,62 @@ "node": ">= 0.8" } }, + "node_modules/swagger-jsdoc": { + "version": "6.2.8", + "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-6.2.8.tgz", + "integrity": "sha512-VPvil1+JRpmJ55CgAtn8DIcpBs0bL5L3q5bVQvF4tAW/k/9JYSj7dCpaYCAv5rufe0vcCbBRQXGvzpkWjvLklQ==", + "license": "MIT", + "dependencies": { + "commander": "6.2.0", + "doctrine": "3.0.0", + "glob": "7.1.6", + "lodash.mergewith": "^4.6.2", + "swagger-parser": "^10.0.3", + "yaml": "2.0.0-1" + }, + "bin": { + "swagger-jsdoc": "bin/swagger-jsdoc.js" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/swagger-parser": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-10.0.3.tgz", + "integrity": "sha512-nF7oMeL4KypldrQhac8RyHerJeGPD1p2xDh900GPvc+Nk7nWP6jX2FcC7WmkinMoAmoO774+AFXcWsW8gMWEIg==", + "license": "MIT", + "dependencies": { + "@apidevtools/swagger-parser": "10.0.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/swagger-ui-dist": { + "version": "5.31.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.31.0.tgz", + "integrity": "sha512-zSUTIck02fSga6rc0RZP3b7J7wgHXwLea8ZjgLA3Vgnb8QeOl3Wou2/j5QkzSGeoz6HusP/coYuJl33aQxQZpg==", + "license": "Apache-2.0", + "dependencies": { + "@scarf/scarf": "=1.4.0" + } + }, + "node_modules/swagger-ui-express": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-5.0.1.tgz", + "integrity": "sha512-SrNU3RiBGTLLmFU8GIJdOdanJTl4TOmT27tt3bWWHppqYmAZ6IDuEuBvMU6nZq0zLEe6b/1rACXCgLZqO6ZfrA==", + "license": "MIT", + "dependencies": { + "swagger-ui-dist": ">=5.0.0" + }, + "engines": { + "node": ">= v0.10.32" + }, + "peerDependencies": { + "express": ">=4.0.0 || >=5.0.0-beta" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -831,6 +1114,15 @@ "node": ">= 0.8" } }, + "node_modules/validator": { + "version": "13.15.26", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.26.tgz", + "integrity": "sha512-spH26xU080ydGggxRyR1Yhcbgx+j3y5jbNXk/8L+iRvdIEQ4uTRH2Sgf2dokud6Q4oAtsbNvJ1Ft+9xmm6IZcA==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -845,6 +1137,45 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" + }, + "node_modules/yaml": { + "version": "2.0.0-1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.0.0-1.tgz", + "integrity": "sha512-W7h5dEhywMKenDJh2iX/LABkbFnBxasD27oyXWDS/feDsxiw0dD5ncXdYXgkvAsXIY2MpW/ZKkr9IU30DBdMNQ==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, + "node_modules/z-schema": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-5.0.5.tgz", + "integrity": "sha512-D7eujBWkLa3p2sIpJA0d1pr7es+a7m0vFAnZLlCEKq/Ij2k0MLi9Br2UPxoxdYystm5K1yeBGzub0FlYUEWj2Q==", + "license": "MIT", + "dependencies": { + "lodash.get": "^4.4.2", + "lodash.isequal": "^4.5.0", + "validator": "^13.7.0" + }, + "bin": { + "z-schema": "bin/z-schema" + }, + "engines": { + "node": ">=8.0.0" + }, + "optionalDependencies": { + "commander": "^9.4.1" + } + }, + "node_modules/z-schema/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^12.20.0 || >=14" + } } } } diff --git a/backend/package.json b/backend/package.json index 6f2c621..c18605e 100644 --- a/backend/package.json +++ b/backend/package.json @@ -12,6 +12,8 @@ "type": "module", "dependencies": { "cors": "^2.8.5", - "express": "^5.2.1" + "express": "^5.2.1", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1" } } diff --git a/backend/src/app.js b/backend/src/app.js index 51d0b37..eac46db 100644 --- a/backend/src/app.js +++ b/backend/src/app.js @@ -1,21 +1,19 @@ import express from 'express'; import { json } from 'express'; -// cors - middleware для разрешения кросс-доменных запросов import cors from 'cors'; -// Эти модули нужны для работы с путями файлов import { fileURLToPath } from 'url'; import { dirname, join } from 'path'; -// Получаем текущий путь к файлу (специально для модулей ES6) const __filename = fileURLToPath(import.meta.url); -// Получаем директорию текущего файла const __dirname = dirname(__filename); // Создаем экземпляр приложения Express const app = express(); -// Подключаем middleware (промежуточное программное обеспечение) -// CORS разрешает запросы с других доменов +// Подключаем наши middleware (промежуточные обработчики маршрутов) + +// middlware CORS разрешает запросы с других доменов. Без этого браузер +// запретит фронтенду общаться с бекендом! app.use(cors({ origin: 'http://localhost:8080', // разрешаем только с этого домена methods: ['GET', 'POST', 'PUT', 'DELETE'], // разрешаем только эти методы @@ -247,7 +245,8 @@ app.get('/', function(_, response) { getOneNote: 'GET /notes/:id', createNote: 'POST /notes', updateNote: 'PUT /notes/:id', - deleteNote: 'DELETE /notes/:id' + deleteNote: 'DELETE /notes/:id', + docs: 'GET /docs' }, instructions: 'Используйте Postman или curl для тестирования API' }); diff --git a/backend/src/server.js b/backend/src/server.js index 6c815a6..b753089 100644 --- a/backend/src/server.js +++ b/backend/src/server.js @@ -1,20 +1,16 @@ // Импортируем настроенное приложение Express из app.js import app from './app.js'; +import setupSwagger from './swagger.js'; // Импортируем настройку Swagger // Определяем порт для сервера // process.env.PORT - берет порт из переменных окружения (если есть) // || 3000 - если переменной нет, используем порт 3000 const PORT = process.env.PORT || 3000; +setupSwagger(app); // Запускаем сервер на указанном порту app.listen(PORT, function() { // Эта функция выполнится при успешном запуске сервера console.log(`Backend server running on port ${PORT}`); console.log(`Вы можете открыть в браузере: http://localhost:${PORT}`); - console.log('Для тестирования API используйте:'); - console.log('1. GET http://localhost:3000/notes - получить все заметки'); - console.log('2. GET http://localhost:3000/notes/1 - получить заметку с ID=1'); - console.log('3. POST http://localhost:3000/notes - создать новую заметку'); - console.log('4. PUT http://localhost:3000/notes/1 - обновить заметку с ID=1'); - console.log('5. DELETE http://localhost:3000/notes/1 - удалить заметку с ID=1'); }); diff --git a/backend/src/swagger.js b/backend/src/swagger.js new file mode 100644 index 0000000..495b71f --- /dev/null +++ b/backend/src/swagger.js @@ -0,0 +1,580 @@ +import swaggerJsdoc from 'swagger-jsdoc'; +import swaggerUi from 'swagger-ui-express'; + +// Определение схем OpenAPI +const swaggerDefinition = { + openapi: '3.0.0', + info: { + title: 'API для управления заметками', + version: '1.0.0', + description: 'Простое REST API для создания, чтения, обновления и удаления заметок', + contact: { + name: 'Разработчик API', + email: 'dev@example.com' + }, + license: { + name: 'MIT', + url: 'https://opensource.org/licenses/MIT' + } + }, + servers: [ + { + url: 'http://localhost:3000', + description: 'Локальный сервер разработки' + }, + { + url: 'https://api.example.com', + description: 'Продакшен сервер' + } + ], + tags: [ + { + name: 'Заметки', + description: 'Операции с заметками' + }, + { + name: 'Системные', + description: 'Системные endpoints' + } + ], + components: { + schemas: { + Note: { + type: 'object', + required: ['title', 'text', 'author'], + properties: { + id: { + type: 'integer', + example: 1, + description: 'Уникальный идентификатор заметки' + }, + title: { + type: 'string', + example: 'Моя первая заметка', + description: 'Заголовок заметки', + minLength: 1, + maxLength: 100 + }, + text: { + type: 'string', + example: 'Это содержимое моей заметки...', + description: 'Текст заметки', + minLength: 1 + }, + author: { + type: 'string', + example: 'Иван Иванов', + description: 'Автор заметки', + minLength: 1, + maxLength: 50 + }, + createdAt: { + type: 'string', + format: 'date-time', + example: '2023-10-01T12:00:00.000Z', + description: 'Дата и время создания' + }, + updatedAt: { + type: 'string', + format: 'date-time', + example: '2023-10-02T14:30:00.000Z', + description: 'Дата и время последнего обновления' + } + } + }, + NoteInput: { + type: 'object', + required: ['title', 'text', 'author'], + properties: { + title: { + type: 'string', + example: 'Моя первая заметка', + description: 'Заголовок заметки' + }, + text: { + type: 'string', + example: 'Это содержимое мой заметки...', + description: 'Текст заметки' + }, + author: { + type: 'string', + example: 'Иван Иванов', + description: 'Автор заметки' + } + } + }, + NoteUpdate: { + type: 'object', + properties: { + title: { + type: 'string', + example: 'Обновленный заголовок', + description: 'Новый заголовок заметки' + }, + text: { + type: 'string', + example: 'Обновленный текст заметки...', + description: 'Новый текст заметки' + }, + author: { + type: 'string', + example: 'Петр Петров', + description: 'Новый автор заметки' + } + } + }, + SuccessResponse: { + type: 'object', + properties: { + success: { + type: 'boolean', + example: true + }, + message: { + type: 'string', + example: 'Операция выполнена успешно' + }, + data: { + oneOf: [ + { $ref: '#/components/schemas/Note' }, + { type: 'array', items: { $ref: '#/components/schemas/Note' } } + ] + }, + count: { + type: 'integer', + example: 5 + } + } + }, + ErrorResponse: { + type: 'object', + properties: { + success: { + type: 'boolean', + example: false + }, + message: { + type: 'string', + example: 'Произошла ошибка' + } + } + } + }, + securitySchemes: { + BearerAuth: { + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT' + } + } + }, + paths: {} +}; + +// Опции для swagger-jsdoc +const options = { + swaggerDefinition, + apis: [], // Пути к файлам с JSDoc комментариями (если будут использоваться) +}; + +// Генерируем спецификацию +const swaggerSpec = swaggerJsdoc(options); + +// Расширяем спецификацию описанием endpoints +swaggerSpec.paths = { + '/': { + get: { + tags: ['Системные'], + summary: 'Проверка работы сервера', + description: 'Возвращает приветственное сообщение и список доступных endpoints', + responses: { + 200: { + description: 'Сервер работает', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + message: { type: 'string' }, + endpoints: { type: 'object' }, + instructions: { type: 'string' } + } + }, + example: { + message: 'Добро пожаловать в API для заметок!', + endpoints: { + getAllNotes: 'GET /notes', + getOneNote: 'GET /notes/:id', + createNote: 'POST /notes', + updateNote: 'PUT /notes/:id', + deleteNote: 'DELETE /notes/:id' + }, + instructions: 'Используйте Postman или curl для тестирования API' + } + } + } + } + } + } + }, + '/notes': { + get: { + tags: ['Заметки'], + summary: 'Получить все заметки', + description: 'Возвращает список всех заметок с пагинацией', + parameters: [ + { + name: 'page', + in: 'query', + description: 'Номер страницы', + required: false, + schema: { + type: 'integer', + minimum: 1, + default: 1 + } + }, + { + name: 'limit', + in: 'query', + description: 'Количество заметок на странице', + required: false, + schema: { + type: 'integer', + minimum: 1, + maximum: 100, + default: 10 + } + } + ], + responses: { + 200: { + description: 'Список заметок успешно получен', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/SuccessResponse' + }, + example: { + success: true, + data: [ + { + id: 1, + title: 'Первая заметка', + text: 'Содержимое первой заметки', + author: 'Иван Иванов', + createdAt: '2023-10-01T12:00:00.000Z', + updatedAt: '2023-10-01T12:00:00.000Z' + }, + { + id: 2, + title: 'Вторая заметка', + text: 'Содержимое второй заметки', + author: 'Петр Петров', + createdAt: '2023-10-02T14:30:00.000Z', + updatedAt: '2023-10-02T14:30:00.000Z' + } + ], + count: 2 + } + } + } + }, + 500: { + description: 'Внутренняя ошибка сервера', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/ErrorResponse' + }, + example: { + success: false, + message: 'Произошла внутренняя ошибка сервера' + } + } + } + } + } + }, + post: { + tags: ['Заметки'], + summary: 'Создать новую заметку', + description: 'Создает новую заметку с указанными данными', + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/NoteInput' + }, + example: { + title: 'Новая заметка', + text: 'Содержимое новой заметки...', + author: 'Иван Иванов' + } + } + } + }, + responses: { + 201: { + description: 'Заметка успешно создана', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/SuccessResponse' + }, + example: { + success: true, + message: 'Заметка успешно создана', + data: { + id: 3, + title: 'Новая заметка', + text: 'Содержимое новой заметки...', + author: 'Иван Иванов', + createdAt: '2023-10-03T10:15:00.000Z', + updatedAt: '2023-10-03T10:15:00.000Z' + } + } + } + } + }, + 400: { + description: 'Неверные данные запроса', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/ErrorResponse' + }, + example: { + success: false, + message: 'Пожалуйста, заполните все поля: title, text, author' + } + } + } + }, + 500: { + description: 'Внутренняя ошибка сервера' + } + } + } + }, + '/notes/{id}': { + get: { + tags: ['Заметки'], + summary: 'Получить заметку по ID', + description: 'Возвращает заметку по указанному идентификатору', + parameters: [ + { + name: 'id', + in: 'path', + description: 'ID заметки', + required: true, + schema: { + type: 'integer', + minimum: 1 + } + } + ], + responses: { + 200: { + description: 'Заметка успешно найдена', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/SuccessResponse' + }, + example: { + success: true, + data: { + id: 1, + title: 'Первая заметка', + text: 'Содержимое первой заметки', + author: 'Иван Иванов', + createdAt: '2023-10-01T12:00:00.000Z', + updatedAt: '2023-10-01T12:00:00.000Z' + } + } + } + } + }, + 404: { + description: 'Заметка не найдена', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/ErrorResponse' + }, + example: { + success: false, + message: 'Заметка с таким ID не найдена' + } + } + } + }, + 400: { + description: 'Неверный формат ID' + }, + 500: { + description: 'Внутренняя ошибка сервера' + } + } + }, + put: { + tags: ['Заметки'], + summary: 'Обновить заметку', + description: 'Обновляет существующую заметку по указанному ID', + parameters: [ + { + name: 'id', + in: 'path', + description: 'ID заметки для обновления', + required: true, + schema: { + type: 'integer', + minimum: 1 + } + } + ], + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/NoteUpdate' + }, + example: { + title: 'Обновленный заголовок', + text: 'Обновленное содержимое заметки...', + author: 'Петр Петров' + } + } + } + }, + responses: { + 200: { + description: 'Заметка успешно обновлена', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/SuccessResponse' + }, + example: { + success: true, + message: 'Заметка успешно обновлена', + data: { + id: 1, + title: 'Обновленный заголовок', + text: 'Обновленное содержимое заметки...', + author: 'Петр Петров', + createdAt: '2023-10-01T12:00:00.000Z', + updatedAt: '2023-10-03T15:45:00.000Z' + } + } + } + } + }, + 400: { + description: 'Неверные данные запроса', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/ErrorResponse' + } + } + } + }, + 404: { + description: 'Заметка не найдена', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/ErrorResponse' + }, + example: { + success: false, + message: 'Заметка с таким ID не найдена' + } + } + } + }, + 500: { + description: 'Внутренняя ошибка сервера' + } + } + }, + delete: { + tags: ['Заметки'], + summary: 'Удалить заметку', + description: 'Удаляет заметку по указанному ID', + parameters: [ + { + name: 'id', + in: 'path', + description: 'ID заметки для удаления', + required: true, + schema: { + type: 'integer', + minimum: 1 + } + } + ], + responses: { + 200: { + description: 'Заметка успешно удалена', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/SuccessResponse' + }, + example: { + success: true, + message: 'Заметка успешно удалена', + data: { + id: 1, + title: 'Первая заметка', + text: 'Содержимое первой заметки', + author: 'Иван Иванов', + createdAt: '2023-10-01T12:00:00.000Z', + updatedAt: '2023-10-01T12:00:00.000Z' + } + } + } + } + }, + 404: { + description: 'Заметка не найдена', + content: { + 'application/json': { + schema: { + $ref: '#/components/schemas/ErrorResponse' + }, + example: { + success: false, + message: 'Заметка с таким ID не найдена' + } + } + } + }, + 500: { + description: 'Внутренняя ошибка сервера' + } + } + } + } +}; + +// Функция для настройки Swagger UI +const setupSwagger = (app) => { + app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec)); + + // Дополнительный endpoint для получения сырой спецификации JSON + app.get('/docs.json', (_, res) => { + res.setHeader('Content-Type', 'application/json'); + res.send(swaggerSpec); + }); + + console.log('📚 Swagger документация доступна по адресу: http://localhost:3000/docs'); + console.log('📄 Swagger спецификация доступна по адресу: http://localhost:3000/docs.json'); +}; + +export default setupSwagger; diff --git a/frontend/src/public/config.json b/frontend/src/public/config.json deleted file mode 100644 index a8f3a2e..0000000 --- a/frontend/src/public/config.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "backendUrl": "http://localhost:3000", - "apiPrefix": "", - "version": "1.0.0" -} diff --git a/frontend/src/server.js b/frontend/src/server.js index 543641d..96fa028 100644 --- a/frontend/src/server.js +++ b/frontend/src/server.js @@ -14,7 +14,9 @@ const __dirname = dirname(__filename); // файлу в public, то буден выдан данный файл app.use(express.static(join(__dirname, 'public'))); -// Конфигурационный endpoint +// Маршрут с конфигурацией. +// Здесь javascript фронтенда может узнать, по какому адресу +// можно найти API бекенда app.get('/config.json', (_, res) => { res.json({ backendUrl: BACKEND_URL,