diff --git a/.env.dist b/.env.dist index 977badc..d2823c3 100644 --- a/.env.dist +++ b/.env.dist @@ -1 +1,2 @@ DB_PATH=./data/database.db +API_FOOTBALL_DATA= diff --git a/.eslintrc.json b/.eslintrc.json index 17ed29a..e2edae8 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,7 +1,11 @@ { "extends": ["airbnb-base", "prettier"], - "plugins": ["prettier"], + "plugins": ["prettier", "jest"], "rules": { - "prettier/prettier": ["error"] + "prettier/prettier": ["error"], + "class-methods-use-this": "off" + }, + "env": { + "jest/globals": true } } diff --git a/data/.gitignore b/data/.gitignore new file mode 100644 index 0000000..dbf4200 --- /dev/null +++ b/data/.gitignore @@ -0,0 +1 @@ +database.db diff --git a/data/datatbase.db b/data/datatbase.db deleted file mode 100644 index e69de29..0000000 diff --git a/package-lock.json b/package-lock.json index fb091f1..20bc219 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,29 +14,35 @@ } }, "@babel/core": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.10.4.tgz", - "integrity": "sha512-3A0tS0HWpy4XujGc7QtOIHTeNwUgWaZc/WuS5YQrfhU67jnVmsD6OGPc1AKHH0LJHQICGncy3+YUjIhVlfDdcA==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.10.5.tgz", + "integrity": "sha512-O34LQooYVDXPl7QWCdW9p4NR+QlzOr7xShPPJz8GsuCU3/8ua/wqTr7gmnxXv+WBESiGU/G5s16i6tUvHkNb+w==", "dev": true, "requires": { "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.10.4", - "@babel/helper-module-transforms": "^7.10.4", + "@babel/generator": "^7.10.5", + "@babel/helper-module-transforms": "^7.10.5", "@babel/helpers": "^7.10.4", - "@babel/parser": "^7.10.4", + "@babel/parser": "^7.10.5", "@babel/template": "^7.10.4", - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4", + "@babel/traverse": "^7.10.5", + "@babel/types": "^7.10.5", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.1", "json5": "^2.1.2", - "lodash": "^4.17.13", + "lodash": "^4.17.19", "resolve": "^1.3.2", "semver": "^5.4.1", "source-map": "^0.5.0" }, "dependencies": { + "lodash": { + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", + "dev": true + }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -46,14 +52,13 @@ } }, "@babel/generator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.4.tgz", - "integrity": "sha512-toLIHUIAgcQygFZRAQcsLQV3CBuX6yOIru1kJk/qqqvcRmZrYe6WavZTSG+bB8MxhnL9YPf+pKQfuiP161q7ng==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.10.5.tgz", + "integrity": "sha512-3vXxr3FEW7E7lJZiWQ3bM4+v/Vyr9C+hpolQ8BGFr9Y8Ri2tFLWTixmwKBafDujO1WVah4fhZBeU1bieKdghig==", "dev": true, "requires": { - "@babel/types": "^7.10.4", + "@babel/types": "^7.10.5", "jsesc": "^2.5.1", - "lodash": "^4.17.13", "source-map": "^0.5.0" }, "dependencies": { @@ -86,12 +91,12 @@ } }, "@babel/helper-member-expression-to-functions": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.4.tgz", - "integrity": "sha512-m5j85pK/KZhuSdM/8cHUABQTAslV47OjfIB9Cc7P+PvlAoBzdb79BGNfw8RhT5Mq3p+xGd0ZfAKixbrUZx0C7A==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.10.5.tgz", + "integrity": "sha512-HiqJpYD5+WopCXIAbQDG0zye5XYVvcO9w/DHp5GsaGkRUaamLj2bEtu6i8rnGGprAhHM3qidCMgp71HF4endhA==", "dev": true, "requires": { - "@babel/types": "^7.10.4" + "@babel/types": "^7.10.5" } }, "@babel/helper-module-imports": { @@ -104,9 +109,9 @@ } }, "@babel/helper-module-transforms": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.10.4.tgz", - "integrity": "sha512-Er2FQX0oa3nV7eM1o0tNCTx7izmQtwAQsIiaLRWtavAAEcskb0XJ5OjJbVrYXWOTr8om921Scabn4/tzlx7j1Q==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.10.5.tgz", + "integrity": "sha512-4P+CWMJ6/j1W915ITJaUkadLObmCRRSC234uctJfn/vHrsLNxsR8dwlcXv9ZhJWzl77awf+mWXSZEKt5t0OnlA==", "dev": true, "requires": { "@babel/helper-module-imports": "^7.10.4", @@ -114,8 +119,16 @@ "@babel/helper-simple-access": "^7.10.4", "@babel/helper-split-export-declaration": "^7.10.4", "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4", - "lodash": "^4.17.13" + "@babel/types": "^7.10.5", + "lodash": "^4.17.19" + }, + "dependencies": { + "lodash": { + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", + "dev": true + } } }, "@babel/helper-optimise-call-expression": { @@ -193,9 +206,9 @@ } }, "@babel/parser": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.4.tgz", - "integrity": "sha512-8jHII4hf+YVDsskTF6WuMB3X4Eh+PsUkC2ljq22so5rHvH+T8BzyL94VOdyFLNR8tBSVXOTbNHOKpR4TfRxVtA==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.10.5.tgz", + "integrity": "sha512-wfryxy4bE1UivvQKSQDU4/X6dr+i8bctjUjj8Zyt3DQy7NtPizJXT8M52nqpNKL+nq2PW8lxk4ZqLj0fD4B4hQ==", "dev": true }, "@babel/plugin-syntax-async-generators": { @@ -297,16 +310,6 @@ "@babel/helper-plugin-utils": "^7.8.0" } }, - "@babel/runtime-corejs3": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/runtime-corejs3/-/runtime-corejs3-7.10.4.tgz", - "integrity": "sha512-BFlgP2SoLO9HJX9WBwN67gHWMBhDX/eDz64Jajd6mR/UAUzqrNMm99d4qHnVaKscAElZoFiPv+JpR/Siud5lXw==", - "dev": true, - "requires": { - "core-js-pure": "^3.0.0", - "regenerator-runtime": "^0.13.4" - } - }, "@babel/template": { "version": "7.10.4", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", @@ -319,31 +322,47 @@ } }, "@babel/traverse": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.4.tgz", - "integrity": "sha512-aSy7p5THgSYm4YyxNGz6jZpXf+Ok40QF3aA2LyIONkDHpAcJzDUqlCKXv6peqYUs2gmic849C/t2HKw2a2K20Q==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.10.5.tgz", + "integrity": "sha512-yc/fyv2gUjPqzTz0WHeRJH2pv7jA9kA7mBX2tXl/x5iOE81uaVPuGPtaYk7wmkx4b67mQ7NqI8rmT2pF47KYKQ==", "dev": true, "requires": { "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.10.4", + "@babel/generator": "^7.10.5", "@babel/helper-function-name": "^7.10.4", "@babel/helper-split-export-declaration": "^7.10.4", - "@babel/parser": "^7.10.4", - "@babel/types": "^7.10.4", + "@babel/parser": "^7.10.5", + "@babel/types": "^7.10.5", "debug": "^4.1.0", "globals": "^11.1.0", - "lodash": "^4.17.13" + "lodash": "^4.17.19" + }, + "dependencies": { + "lodash": { + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", + "dev": true + } } }, "@babel/types": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.4.tgz", - "integrity": "sha512-UTCFOxC3FsFHb7lkRMVvgLzaRVamXuAs2Tz4wajva4WxtVY82eZeaUBtC2Zt95FU9TiznuC0Zk35tsim8jeVpg==", + "version": "7.10.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.10.5.tgz", + "integrity": "sha512-ixV66KWfCI6GKoA/2H9v6bQdbfXEwwpOdQ8cRvb4F+eyvhlaHxWFMQB4+3d9QFJXZsiiiqVrewNV0DFEQpyT4Q==", "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.10.4", - "lodash": "^4.17.13", + "lodash": "^4.17.19", "to-fast-properties": "^2.0.0" + }, + "dependencies": { + "lodash": { + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==", + "dev": true + } } }, "@bcoe/v8-coverage": { @@ -890,9 +909,9 @@ "dev": true }, "@sinonjs/commons": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.0.tgz", - "integrity": "sha512-wEj54PfsZ5jGSwMX68G8ZXFawcSglQSXqCftWX3ec8MDUzQdHgcKvw97awHbY0efQEL5iKUOAmmVtoYgmrSG4Q==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", + "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==", "dev": true, "requires": { "type-detect": "4.0.8" @@ -949,9 +968,9 @@ } }, "@types/babel__traverse": { - "version": "7.0.12", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.12.tgz", - "integrity": "sha512-t4CoEokHTfcyfb4hUaF9oOHu9RmmNWnm1CP0YmMqOOfClKascOmvlEM736vlqeScuGvBDsHkf8R2INd4DWreQA==", + "version": "7.0.13", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.0.13.tgz", + "integrity": "sha512-i+zS7t6/s9cdQvbqKDARrcbrPvtJGlbYsMkazo03nTAK3RX9FNrLllXys22uiTGJapPOTZTQ35nHh4ISph4SLQ==", "dev": true, "requires": { "@babel/types": "^7.3.0" @@ -997,6 +1016,113 @@ "@types/istanbul-lib-report": "*" } }, + "@types/jest": { + "version": "26.0.5", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-26.0.5.tgz", + "integrity": "sha512-heU+7w8snfwfjtcj2H458aTx3m5unIToOJhx75ebHilBiiQ39OIdA18WkG4LP08YKeAoWAGvWg8s+22w/PeJ6w==", + "dev": true, + "requires": { + "jest-diff": "^25.2.1", + "pretty-format": "^25.2.1" + }, + "dependencies": { + "@jest/types": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-25.5.0.tgz", + "integrity": "sha512-OXD0RgQ86Tu3MazKo8bnrkDRaDXXMGUqd+kTtLtK1Zb7CRzQcaSRPPPV37SvYTdevXEBVxe0HXylEjs8ibkmCw==", + "dev": true, + "requires": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^15.0.0", + "chalk": "^3.0.0" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true + }, + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-3.0.0.tgz", + "integrity": "sha512-4D3B6Wf41KOYRFdszmDqMCGq5VV/uMAB273JILmO+3jAlh8X4qDtdtgCR3fxtbLEMzSx22QdhnDcJvu2u1fVwg==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "diff-sequences": { + "version": "25.2.6", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-25.2.6.tgz", + "integrity": "sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==", + "dev": true + }, + "jest-diff": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-25.5.0.tgz", + "integrity": "sha512-z1kygetuPiREYdNIumRpAHY6RXiGmp70YHptjdaxTWGmA085W3iCnXNx0DhflK3vwrKmrRWyY1wUpkPMVxMK7A==", + "dev": true, + "requires": { + "chalk": "^3.0.0", + "diff-sequences": "^25.2.6", + "jest-get-type": "^25.2.6", + "pretty-format": "^25.5.0" + } + }, + "jest-get-type": { + "version": "25.2.6", + "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz", + "integrity": "sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==", + "dev": true + }, + "pretty-format": { + "version": "25.5.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-25.5.0.tgz", + "integrity": "sha512-kbo/kq2LQ/A/is0PQwsEHM7Ca6//bGPPvU6UnsdDRSKTWxT/ru/xb88v4BJf6a69H+uTytOEsTusT9ksd/1iWQ==", + "dev": true, + "requires": { + "@jest/types": "^25.5.0", + "ansi-regex": "^5.0.0", + "ansi-styles": "^4.0.0", + "react-is": "^16.12.0" + } + } + } + }, + "@types/json-schema": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.5.tgz", + "integrity": "sha512-7+2BITlgjgDhH0vvwZU/HZJVyk+2XUlvxXe8dFMedNX/aMkaOq++rMAFXc0tM7ij15QaWlbdQASBR9dihi+bDQ==", + "dev": true + }, "@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", @@ -1015,9 +1141,9 @@ "dev": true }, "@types/prettier": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.0.1.tgz", - "integrity": "sha512-boy4xPNEtiw6N3abRhBi/e7hNvy3Tt8E9ZRAQrwAGzoCGZS/1wjo9KY7JHhnfnEsG5wSjDbymCozUM9a3ea7OQ==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.0.2.tgz", + "integrity": "sha512-IkVfat549ggtkZUthUzEX49562eGikhSYeVGX97SkMFn+sTZrgRewXjQ4tPKFPCykZHkX1Zfd9OoELGqKU2jJA==", "dev": true }, "@types/sinonjs__fake-timers": { @@ -1053,6 +1179,41 @@ "integrity": "sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==", "dev": true }, + "@typescript-eslint/experimental-utils": { + "version": "2.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-2.34.0.tgz", + "integrity": "sha512-eS6FTkq+wuMJ+sgtuNTtcqavWXqsflWcfBnlYhg/nS4aZ1leewkXGbvBhaapn1q6qf4M71bsR1tez5JTRMuqwA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.3", + "@typescript-eslint/typescript-estree": "2.34.0", + "eslint-scope": "^5.0.0", + "eslint-utils": "^2.0.0" + } + }, + "@typescript-eslint/typescript-estree": { + "version": "2.34.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-2.34.0.tgz", + "integrity": "sha512-OMAr+nJWKdlVM9LOqCqh3pQQPwxHAN7Du8DR6dmwCrAmxtiXQnhHJ6tBNtf+cggqfo51SG/FCwnKhXCIM7hnVg==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "eslint-visitor-keys": "^1.1.0", + "glob": "^7.1.6", + "is-glob": "^4.0.1", + "lodash": "^4.17.15", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + }, + "dependencies": { + "semver": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", + "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", + "dev": true + } + } + }, "a-sync-waterfall": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz", @@ -1333,6 +1494,14 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz", "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==" }, + "axios": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.2.tgz", + "integrity": "sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==", + "requires": { + "follow-redirects": "1.5.10" + } + }, "babel-jest": { "version": "26.1.0", "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.1.0.tgz", @@ -2216,12 +2385,6 @@ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", "dev": true }, - "core-js-pure": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.6.5.tgz", - "integrity": "sha512-lacdXOimsiD0QyNf9BC/mxivNJ/ybBGJXQFKzRekp1WTHoVUWsUHEn+2T8GJAzzIhyOuXA+gOxCVN3l+5PLPUA==", - "dev": true - }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -2312,6 +2475,14 @@ "untildify": "4.0.0", "url": "0.11.0", "yauzl": "2.10.0" + }, + "dependencies": { + "lodash": { + "version": "4.17.15", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", + "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", + "dev": true + } } }, "dashdash": { @@ -2348,13 +2519,10 @@ } }, "decamelize": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-3.2.0.tgz", - "integrity": "sha512-4TgkVUsmmu7oCSyGBm5FvfMoACuoh9EOidm7V5/J2X2djAwwt57qb3F2KMP2ITqODTCSwb+YRV+0Zqrv18k/hw==", - "dev": true, - "requires": { - "xregexp": "^4.2.4" - } + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true }, "decimal.js": { "version": "10.2.0", @@ -2513,6 +2681,11 @@ "is-obj": "^2.0.0" } }, + "dotenv": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", + "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==" + }, "dottie": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.2.tgz", @@ -3080,6 +3253,15 @@ } } }, + "eslint-plugin-jest": { + "version": "23.18.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-23.18.0.tgz", + "integrity": "sha512-wLPM/Rm1SGhxrFQ2TKM/BYsYPhn7ch6ZEK92S2o/vGkAAnDXM0I4nTIo745RIX+VlCRMFgBuJEax6XfTHMdeKg==", + "dev": true, + "requires": { + "@typescript-eslint/experimental-utils": "^2.5.0" + } + }, "eslint-plugin-prettier": { "version": "3.1.4", "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.4.tgz", @@ -3656,6 +3838,29 @@ "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", "dev": true }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + } + } + }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", @@ -4779,9 +4984,9 @@ } }, "execa": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.2.tgz", - "integrity": "sha512-QI2zLa6CjGWdiQsmSkZoGtDx2N+cQIGb3yNolGTdjSQzydzLgYYf8LRuagp7S7fPimjcrzUDSUFd/MgzELMi4Q==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.0.3.tgz", + "integrity": "sha512-WFDXGHckXPWZX19t1kCsXzOpqX9LWYNqn4C+HqZlk/V0imTkzJZqf87ZBhvpHaftERYknpk0fjSylnXVlVgI0A==", "dev": true, "requires": { "cross-spawn": "^7.0.0", @@ -5776,9 +5981,9 @@ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, "jsdom": { - "version": "16.2.2", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.2.2.tgz", - "integrity": "sha512-pDFQbcYtKBHxRaP55zGXCJWgFHkDAYbKcsXEK/3Icu9nKYZkutUXfLBwbD+09XDutkYSHcgfQLZ0qvpAAm9mvg==", + "version": "16.3.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.3.0.tgz", + "integrity": "sha512-zggeX5UuEknpdZzv15+MS1dPYG0J/TftiiNunOeNxSl3qr8Z6cIlQpN0IdJa44z9aFxZRIVqRncvEhQ7X5DtZg==", "dev": true, "requires": { "abab": "^2.0.3", @@ -5801,7 +6006,7 @@ "tough-cookie": "^3.0.1", "w3c-hr-time": "^1.0.2", "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.0.0", + "webidl-conversions": "^6.1.0", "whatwg-encoding": "^1.0.5", "whatwg-mimetype": "^2.3.0", "whatwg-url": "^8.0.0", @@ -6107,9 +6312,9 @@ } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + "version": "4.17.19", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", + "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" }, "lodash.once": { "version": "4.1.1", @@ -7299,12 +7504,6 @@ "picomatch": "^2.2.1" } }, - "regenerator-runtime": { - "version": "0.13.5", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", - "dev": true - }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", @@ -8622,6 +8821,15 @@ "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==", "dev": true }, + "tsutils": { + "version": "3.17.1", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.17.1.tgz", + "integrity": "sha512-kzeQ5B8H3w60nFY2g8cJIuH7JDpsALXySGtwGJ0p2LSjLgay3NdIpqq5SoOBe46bKDW2iq25irHCr8wjomUS2g==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -9182,15 +9390,6 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, - "xregexp": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/xregexp/-/xregexp-4.3.0.tgz", - "integrity": "sha512-7jXDIFXh5yJ/orPn4SXjuVrWWoi4Cr8jfV1eHv9CixKSbU+jY4mxfrBwAuDvupPNKpMUY+FeIqsVw/JLT9+B8g==", - "dev": true, - "requires": { - "@babel/runtime-corejs3": "^7.8.3" - } - }, "y18n": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", @@ -9203,13 +9402,13 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" }, "yargs": { - "version": "15.4.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.0.tgz", - "integrity": "sha512-D3fRFnZwLWp8jVAAhPZBsmeIHY8tTsb8ItV9KaAaopmC6wde2u6Yw29JBIZHXw14kgkRnYmDgmQU4FVMDlIsWw==", + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", "dev": true, "requires": { "cliui": "^6.0.0", - "decamelize": "^3.2.0", + "decamelize": "^1.2.0", "find-up": "^4.1.0", "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", @@ -9263,14 +9462,6 @@ "requires": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" - }, - "dependencies": { - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - } } }, "yauzl": { diff --git a/package.json b/package.json index 09bca52..02b2b70 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,8 @@ "main": "index.js", "scripts": { "start": "node src/app.js", - "dev": "nodemon -e 'js,njk' src/app.js", - "test": "echo \"Error: no test specified\" && exit 1" + "dev": "nodemon -e 'js,njk,html' src/app.js", + "test": "jest" }, "repository": { "type": "git", @@ -19,17 +19,21 @@ }, "homepage": "https://github.com/r-argentina-programa/crud-clubes#readme", "devDependencies": { + "@types/jest": "^26.0.5", "cypress": "^4.9.0", "eslint": "^7.2.0", "eslint-config-airbnb-base": "^14.2.0", "eslint-config-prettier": "^6.11.0", "eslint-plugin-import": "^2.22.0", + "eslint-plugin-jest": "^23.18.0", "eslint-plugin-prettier": "^3.1.4", "jest": "^26.1.0", "nodemon": "^2.0.4", "prettier": "^2.0.5" }, "dependencies": { + "axios": "^0.19.2", + "dotenv": "^8.2.0", "express": "^4.17.1", "nunjucks": "^3.2.1", "rsdi": "^1.0.5", diff --git a/src/app.js b/src/app.js index c8e68ab..38502ff 100644 --- a/src/app.js +++ b/src/app.js @@ -1,3 +1,4 @@ +require('dotenv').config(); const express = require('express'); const nunjucks = require('nunjucks'); const configureDependencyInjection = require('./config/di'); @@ -6,6 +7,7 @@ const { init: initClubModule } = require('./module/club/module'); const app = express(); const port = process.env.PORT || 3000; +app.use(express.urlencoded({ extended: true })); app.use(express.static('public')); // https://mozilla.github.io/nunjucks/getting-started.html#when-using-node diff --git a/src/config/di.js b/src/config/di.js index 574c2f5..72330c5 100644 --- a/src/config/di.js +++ b/src/config/di.js @@ -1,13 +1,19 @@ // configure DI container const { default: DIContainer, object, get, factory } = require('rsdi'); const { Sequelize } = require('sequelize'); -const { ClubController, ClubService, ClubRepository } = require('../module/club/module'); +const { ClubController, ClubService, ClubRepository, ClubModel } = require('../module/club/module'); function configureSequelize() { - return new Sequelize({ + const sequelize = new Sequelize({ dialect: 'sqlite', storage: process.env.DB_PATH, }); + sequelize.sync(); + return sequelize; +} + +function configureClubModule(di) { + return ClubModel.setup(di.get('Sequelize')); } module.exports = function configureDI() { @@ -15,7 +21,8 @@ module.exports = function configureDI() { container.addDefinitions({ ClubController: object(ClubController).construct(get('ClubService')), ClubService: object(ClubService).construct(get('ClubRepository')), - ClubRepository: object(ClubRepository).construct(get('Sequelize')), + ClubRepository: object(ClubRepository).construct(get('ClubModel')), + ClubModel: factory(configureClubModule), Sequelize: factory(configureSequelize), }); return container; diff --git a/src/module/club/controller/__tests__/club.test.js b/src/module/club/controller/__tests__/club.test.js new file mode 100644 index 0000000..b3c2735 --- /dev/null +++ b/src/module/club/controller/__tests__/club.test.js @@ -0,0 +1,40 @@ +const ClubController = require('../club'); + +const serviceMock = { + save: jest.fn(), + delete: jest.fn(), +}; +const controller = new ClubController(serviceMock); + +test('Index renderea index.html', () => { + const renderMock = jest.fn(); + controller.index({}, { render: renderMock }); + expect(renderMock).toHaveBeenCalledTimes(1); + expect(renderMock).toHaveBeenCalledWith('club/view/index.html'); +}); + +test('View renderea form.html', () => { + const renderMock = jest.fn(); + controller.view({}, { render: renderMock }); + expect(renderMock).toHaveBeenCalledTimes(1); + expect(renderMock).toHaveBeenCalledWith('club/view/form.html'); +}); + +test('Save llama al servicio con el body y redirecciona a /club', () => { + const redirectMock = jest.fn(); + const bodyMock = { id: 1 }; + controller.save({ body: bodyMock }, { redirect: redirectMock }); + expect(serviceMock.save).toHaveBeenCalledTimes(1); + expect(serviceMock.save).toHaveBeenCalledWith(bodyMock); + expect(redirectMock).toHaveBeenCalledTimes(1); + expect(redirectMock).toHaveBeenCalledWith('/club'); +}); + +test('Delete llama al servicio con el id del body y redirecciona a /club', () => { + const redirectMock = jest.fn(); + controller.delete({ body: { id: 1 } }, { redirect: redirectMock }); + expect(serviceMock.delete).toHaveBeenCalledTimes(1); + expect(serviceMock.delete).toHaveBeenCalledWith(1); + expect(redirectMock).toHaveBeenCalledTimes(1); + expect(redirectMock).toHaveBeenCalledWith('/club'); +}); diff --git a/src/module/club/controller/club.js b/src/module/club/controller/club.js deleted file mode 100644 index e9d6792..0000000 --- a/src/module/club/controller/club.js +++ /dev/null @@ -1,17 +0,0 @@ -class ClubController { - constructor(clubService) { - this.clubService = clubService; - } - - index(req, res) { - res.render('club/view/index.njk', { nombre: 'test' }); - } - - create(req, res) {} - - update(req, res) {} - - delete(req, res) {} -} - -module.exports = ClubController; diff --git a/src/module/club/controller/clubController.js b/src/module/club/controller/clubController.js new file mode 100644 index 0000000..b901487 --- /dev/null +++ b/src/module/club/controller/clubController.js @@ -0,0 +1,51 @@ +const { formDataToEntity } = require('../mapper/clubMapper'); + +module.exports = class ClubController { + /** + * @param {import('../service/clubService')} clubService + */ + constructor(clubService) { + this.clubService = clubService; + } + + /** + * @param {import('express').Request} req + * @param {import('express').Response} res + */ + async index(req, res) { + const clubs = await this.clubService.getAll(); + res.render('club/view/index.html', { data: { clubs } }); + } + + /** + * @param {import('express').Request} req + * @param {import('express').Response} res + */ + view(req, res) { + res.render('club/view/form.html'); + } + + /** + * @param {import('express').Request} req + * @param {import('express').Response} res + */ + async save(req, res) { + try { + await this.clubService.save(formDataToEntity(req.body)); + res.redirect('/club'); + } catch (e) { + console.error(e); + res.end(); + // res.render('club/view/form.html', { error: e }); + } + } + + /** + * @param {import('express').Request} req + * @param {import('express').Response} res + */ + delete(req, res) { + this.clubService.delete(req.body.id); + res.redirect('/club'); + } +}; diff --git a/src/module/club/entity/club.js b/src/module/club/entity/club.js new file mode 100644 index 0000000..45b0b62 --- /dev/null +++ b/src/module/club/entity/club.js @@ -0,0 +1,29 @@ +module.exports = class Club { + constructor({ + id, + name, + shortName, + tla, + crestUrl, + address, + phone, + website, + email, + founded, + clubColors, + venue, + }) { + this.id = id; + this.name = name; + this.shortName = shortName; + this.tla = tla; + this.crestUrl = crestUrl; + this.address = address; + this.phone = phone; + this.website = website; + this.email = email; + this.founded = founded; + this.clubColors = clubColors; + this.venue = venue; + } +}; diff --git a/src/module/club/job/download-data.js b/src/module/club/job/download-data.js new file mode 100644 index 0000000..267f935 --- /dev/null +++ b/src/module/club/job/download-data.js @@ -0,0 +1,55 @@ +const axios = require('axios'); +const fs = require('fs'); +require('dotenv').config(); + +if (process.env.API_FOOTBALL_DATA.length === 0) { + console.log( + 'Se debe crear un archivo.env (copiar el .env.dist y cambiarle el nombre) y configurar la API key gratuita de https://www.football-data.org/' + ); + process.exit(1); +} + +const API_TOKEN = process.env.API_FOOTBALL_DATA; +const API_BASE = 'http://api.football-data.org/v2'; +const ENGLAND_ID = 2021; + +async function obtenerEquipos() { + console.log('obteniendo equipos...'); + const resultado = await axios.get(`${API_BASE}/competitions/${ENGLAND_ID}/teams`, { + headers: { 'X-Auth-Token': API_TOKEN }, + }); + return resultado.data.teams; +} + +async function obtenerPlantel(id) { + console.log(`obteniendo plantel id ${id}...`); + const respuesta = await axios.get(`${API_BASE}/teams/${id}`, { + headers: { 'X-Auth-Token': API_TOKEN }, + }); + + return respuesta.data; +} + +async function iniciar() { + const DIRECTORIO = './data'; + const archivo = `${DIRECTORIO}/equipos.json`; + + let equipos; + + if (!fs.existsSync(archivo)) { + equipos = await obtenerEquipos(); + fs.writeFileSync(archivo, JSON.stringify(equipos)); + } + + equipos = JSON.parse(fs.readFileSync(archivo)); + + equipos.forEach(async (equipo) => { + const archivoPlantel = `${DIRECTORIO}/equipos/${equipo.tla}.json`; + + if (!fs.existsSync(archivoPlantel)) { + fs.writeFileSync(archivoPlantel, JSON.stringify(await obtenerPlantel(equipo.id))); + } + }); +} + +iniciar(); diff --git a/src/module/club/mapper/clubMapper.js b/src/module/club/mapper/clubMapper.js new file mode 100644 index 0000000..f07622a --- /dev/null +++ b/src/module/club/mapper/clubMapper.js @@ -0,0 +1,43 @@ +// map from Controller to Entity +// map from Entity to Controller +// map from Entity to Model +// map from Model to Entity + +const Club = require('../entity/club'); + +module.exports = { + /** + * + * @param {Object} formData + * @returns Club + */ + formDataToEntity({ + id, + name, + 'short-name': shortName, + tla, + 'crest-url': crestUrl, + address, + phone, + website, + email, + founded, + 'club-colors': clubColors, + venue, + }) { + return new Club({ + id, + name, + shortName, + tla, + crestUrl, + address, + phone, + website, + email, + founded, + clubColors, + venue, + }); + }, +}; diff --git a/src/module/club/model/repository/club.js b/src/module/club/model/repository/club.js deleted file mode 100644 index 346f686..0000000 --- a/src/module/club/model/repository/club.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = class ClubRepository { - constructor(sequelize) { - this.sequelize = sequelize; - } -}; diff --git a/src/module/club/model/service/club.js b/src/module/club/model/service/club.js deleted file mode 100644 index adec072..0000000 --- a/src/module/club/model/service/club.js +++ /dev/null @@ -1,5 +0,0 @@ -module.exports = class Service { - constructor(clubRepository) { - this.clubRepository = clubRepository; - } -}; diff --git a/src/module/club/module.js b/src/module/club/module.js index f90694f..5131ece 100644 --- a/src/module/club/module.js +++ b/src/module/club/module.js @@ -1,7 +1,8 @@ const configureRoutes = require('./routes'); -const ClubController = require('./controller/club'); -const ClubRepository = require('./model/repository/club'); -const ClubService = require('./model/service/club'); +const ClubController = require('./controller/clubController'); +const ClubRepository = require('./repository/sqlite/clubRepository'); +const ClubService = require('./service/clubService'); +const ClubModel = require('./repository/sqlite/clubModel'); function init(app, container) { configureRoutes(container.get('ClubController'), app); @@ -12,4 +13,5 @@ module.exports = { ClubController, ClubService, ClubRepository, + ClubModel, }; diff --git a/src/module/club/repository/abstractClubRepository.js b/src/module/club/repository/abstractClubRepository.js new file mode 100644 index 0000000..a9b6766 --- /dev/null +++ b/src/module/club/repository/abstractClubRepository.js @@ -0,0 +1,31 @@ +/* eslint-disable no-empty-function */ +/* eslint-disable no-unused-vars */ +const AbstractRepositoryError = require('./error/abstractRepositoryError'); + +module.exports = class AbstractClubRepository { + constructor() { + throw new AbstractRepositoryError('No se puede instanciar el repositorio de clubes abstracto.'); + } + + /** + * @param {import('../entity/club')} club + * @returns {import('../entity/club')} + */ + async save(club) {} + + /** + * @param {Number} id + */ + async delete(id) {} + + /** + * @param {Number} id + * @returns {import('../entity/club')} + */ + async get(id) {} + + /** + * @returns {Array} + */ + async getAll() {} +}; diff --git a/src/module/club/repository/error/abstractRepositoryError.js b/src/module/club/repository/error/abstractRepositoryError.js new file mode 100644 index 0000000..1e03de6 --- /dev/null +++ b/src/module/club/repository/error/abstractRepositoryError.js @@ -0,0 +1 @@ +module.exports = class AbstractRepositoryError extends Error {}; diff --git a/src/module/club/repository/sqlite/__test__/club.test.js b/src/module/club/repository/sqlite/__test__/club.test.js new file mode 100644 index 0000000..45d4eef --- /dev/null +++ b/src/module/club/repository/sqlite/__test__/club.test.js @@ -0,0 +1,10 @@ +const Sequelize = require('sequelize'); +const ClubRepository = require('../../club'); + +const sequelize = new Sequelize('sqlite::memory:'); + +const repository = new ClubRepository(sequelize); + +test('Guarda un equipo', () => { + console.log(repository.save()); +}); diff --git a/src/module/club/repository/sqlite/clubMapper.js b/src/module/club/repository/sqlite/clubMapper.js new file mode 100644 index 0000000..af0ca32 --- /dev/null +++ b/src/module/club/repository/sqlite/clubMapper.js @@ -0,0 +1,10 @@ +const Club = require('../../entity/club'); +/** + * @param {import('./clubModel')} model + * @returns {import('../../entity/club')} + */ +module.exports = { + fromModelToEntity(model) { + return new Club(model.toJSON()); + }, +}; diff --git a/src/module/club/repository/sqlite/clubModel.js b/src/module/club/repository/sqlite/clubModel.js new file mode 100644 index 0000000..4bb06cc --- /dev/null +++ b/src/module/club/repository/sqlite/clubModel.js @@ -0,0 +1,67 @@ +const { Sequelize, Model, DataTypes } = require('sequelize'); + +module.exports = class ClubModel extends Model { + /** + * @param {import('sequelize').Sequelize} sequelizeInstance + * @returns {typeof ClubModel} + */ + static setup(sequelizeInstance) { + ClubModel.init( + { + id: { + type: DataTypes.INTEGER, + allowNull: false, + primaryKey: true, + autoIncrement: true, + }, + name: { + type: DataTypes.STRING, + }, + shortName: { + type: DataTypes.STRING, + }, + tla: { + type: DataTypes.STRING, + }, + crestUrl: { + type: DataTypes.STRING, + }, + address: { + type: DataTypes.STRING, + }, + phone: { + type: DataTypes.STRING, + }, + website: { + type: DataTypes.STRING, + }, + email: { + type: DataTypes.STRING, + }, + founded: { + type: DataTypes.INTEGER, + }, + clubColors: { + type: DataTypes.STRING, + }, + venue: { + type: DataTypes.STRING, + }, + lastUpdated: { + type: DataTypes.DATE, + defaultValue: Sequelize.NOW, + }, + createdAt: { + type: DataTypes.DATE, + defaultValue: Sequelize.NOW, + }, + }, + { + sequelize: sequelizeInstance, + modelName: 'Club', + timestamps: false, + } + ); + return ClubModel; + } +}; diff --git a/src/module/club/repository/sqlite/clubRepository.js b/src/module/club/repository/sqlite/clubRepository.js new file mode 100644 index 0000000..2e8a529 --- /dev/null +++ b/src/module/club/repository/sqlite/clubRepository.js @@ -0,0 +1,29 @@ +const { fromModelToEntity } = require('./clubMapper'); + +module.exports = class ClubRepository { + /** + * @param {typeof import('./clubModel')} clubModel + */ + constructor(clubModel) { + this.clubModel = clubModel; + } + + /** + * @param {import('../../entity/club')} club + */ + async save(club) { + return this.clubModel.create(club); + } + + async delete() {} + + async get() {} + + /** + * @return {Promise>} + */ + async getAll() { + const clubs = await this.clubModel.findAll(); + return clubs.map(fromModelToEntity); + } +}; diff --git a/src/module/club/routes.js b/src/module/club/routes.js index 3064fa2..5aa2c76 100644 --- a/src/module/club/routes.js +++ b/src/module/club/routes.js @@ -1,9 +1,15 @@ const ROUTE = '/club'; +/** + * + * @param {import('./controller/club')} controller + * @param {import('express').Application} app + */ module.exports = function routes(controller, app) { - app.get(`/`, controller.index); - app.get(`${ROUTE}`, controller.index); - app.get(`${ROUTE}/create`, controller.create); - app.get(`${ROUTE}/update/:team`, controller.update); - app.get(`${ROUTE}/delete/:team`, controller.delete); + app.get(`/`, controller.index.bind(controller)); + app.get(`${ROUTE}`, controller.index.bind(controller)); + app.get(`${ROUTE}/view`, controller.view.bind(controller)); + app.get(`${ROUTE}/view/:team`, controller.view.bind(controller)); + app.post(`${ROUTE}/save`, controller.save.bind(controller)); + app.get(`${ROUTE}/delete/:team`, controller.delete.bind(controller)); }; diff --git a/src/module/club/service/__tests__/club.test.js b/src/module/club/service/__tests__/club.test.js new file mode 100644 index 0000000..502e02b --- /dev/null +++ b/src/module/club/service/__tests__/club.test.js @@ -0,0 +1,44 @@ +const ClubService = require('../club'); +const ClubNotDefinedError = require('../exception/clubNotDefinedError'); +const IdNotDefinedError = require('../exception/idNotDefinedError'); + +const repositoryMock = { + save: jest.fn(), + delete: jest.fn(), + get: jest.fn(), + getAll: jest.fn(), +}; + +const service = new ClubService(repositoryMock); + +test('Guardar un equipo llama al método save del repositorio 1 vez', () => { + service.save({}); + expect(repositoryMock.save).toHaveBeenCalledTimes(1); +}); + +test('Llamar a guardar un equipo sin pasar un equipo da un error específico', () => { + expect(service.save).toThrowError(ClubNotDefinedError); +}); + +test('Eliminar un equipo llama al método delete del repositorio 1 vez', () => { + service.delete({}); + expect(repositoryMock.delete).toHaveBeenCalledTimes(1); +}); + +test('Llamar a eliminar un equipo sin pasar un equipo da un error específico', () => { + expect(service.delete).toThrowError(ClubNotDefinedError); +}); + +test('Consultar un equipo por id llama al método get del repositorio 1 vez', () => { + service.getById(1); + expect(repositoryMock.get).toHaveBeenCalledTimes(1); +}); + +test('Llamar a consultar un equipo sin pasar un equipo da un error específico', () => { + expect(service.getById).toThrowError(IdNotDefinedError); +}); + +test('Consultar todos los equipos llama al método getAll del repositorio 1 vez', () => { + service.getAll(); + expect(repositoryMock.getAll).toHaveBeenCalledTimes(1); +}); diff --git a/src/module/club/service/clubService.js b/src/module/club/service/clubService.js new file mode 100644 index 0000000..d111cc5 --- /dev/null +++ b/src/module/club/service/clubService.js @@ -0,0 +1,44 @@ +/** + * @typedef {import('../repository/abstractClubRepository')} AbstractClubRepository + */ + +const ClubNotDefinedError = require('./exception/clubNotDefinedError'); +const IdNotDefinedError = require('./exception/idNotDefinedError'); + +module.exports = class Service { + /** + * + * @param {AbstractClubRepository} clubRepository + */ + constructor(clubRepository) { + this.clubRepository = clubRepository; + } + + async save(team) { + if (team === undefined) { + throw new ClubNotDefinedError(); + } + + return this.clubRepository.save(team); + } + + async delete(team) { + if (team === undefined) { + throw new ClubNotDefinedError(); + } + + return this.clubRepository.delete(team); + } + + async getById(id) { + if (id === undefined) { + throw new IdNotDefinedError(); + } + + return this.clubRepository.get(id); + } + + async getAll() { + return this.clubRepository.getAll(); + } +}; diff --git a/src/module/club/service/exception/clubNotDefinedError.js b/src/module/club/service/exception/clubNotDefinedError.js new file mode 100644 index 0000000..48f9f33 --- /dev/null +++ b/src/module/club/service/exception/clubNotDefinedError.js @@ -0,0 +1 @@ +module.exports = class ClubNotDefinedException extends Error {}; diff --git a/src/module/club/service/exception/idNotDefinedError.js b/src/module/club/service/exception/idNotDefinedError.js new file mode 100644 index 0000000..6bd1213 --- /dev/null +++ b/src/module/club/service/exception/idNotDefinedError.js @@ -0,0 +1 @@ +module.exports = class IdNotDefinedError extends Error {}; diff --git a/src/module/club/view/form.html b/src/module/club/view/form.html new file mode 100644 index 0000000..a7a23ae --- /dev/null +++ b/src/module/club/view/form.html @@ -0,0 +1,18 @@ +{% block body %} +
+ + + + + + + + + + + + + + +
+{% endblock %} diff --git a/src/module/club/view/index.html b/src/module/club/view/index.html new file mode 100644 index 0000000..ac27329 --- /dev/null +++ b/src/module/club/view/index.html @@ -0,0 +1,28 @@ +{% extends 'view/layout/base.njk' %} + +{% block body %} +

Índice de clubes

+{% for club in data.clubs %} + + + + + + + + + + + + + + + + + + +
IDNombre
+ Agregar +
{{club.id}}{{club.name}}
+{% endfor %} +{% endblock %} diff --git a/src/module/club/view/index.njk b/src/module/club/view/index.njk deleted file mode 100644 index 338baf9..0000000 --- a/src/module/club/view/index.njk +++ /dev/null @@ -1,5 +0,0 @@ -{% extends 'view/layout/base.njk' %} - -{% block body %} -

Índice de clubes

-{% endblock %}