Commit 1c5e08c3 authored by Ibadullina Inabat's avatar Ibadullina Inabat

Merge branch 'development' of…

Merge branch 'development' of ssh://git.attractor-school.com:30022/apollo64/crm-team-one into task-31-feature/connect_registration_login_with_back
parents 9b487731 bd90d3e8
......@@ -13,6 +13,7 @@
"@types/cors": "^2.8.12",
"@types/express": "^4.17.14",
"bcrypt": "^5.1.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.13.2",
"cors": "^2.8.5",
"express": "^4.18.2",
......@@ -23,12 +24,14 @@
"typeorm": "^0.3.10"
},
"devDependencies": {
"@faker-js/faker": "^7.6.0",
"@types/node": "^18.11.8",
"@typescript-eslint/eslint-plugin": "^5.41.0",
"@typescript-eslint/parser": "^5.41.0",
"eslint": "^8.26.0",
"nodemon": "^2.0.20",
"ts-node": "^10.9.1",
"typeorm-fixtures-cli": "^3.0.1",
"typescript": "^4.8.4"
}
},
......@@ -1392,6 +1395,31 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
},
"node_modules/@faker-js/faker": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-7.6.0.tgz",
"integrity": "sha512-XK6BTq1NDMo9Xqw/YkYyGjSsg44fbNwYRx7QK2CuoQgyy+f1rrTDHoExVM5PsyXCtfl2vs2vVJ0MN0yN6LppRw==",
"dev": true,
"engines": {
"node": ">=14.0.0",
"npm": ">=6.0.0"
}
},
"node_modules/@hapi/hoek": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
"integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==",
"dev": true
},
"node_modules/@hapi/topo": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz",
"integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==",
"dev": true,
"dependencies": {
"@hapi/hoek": "^9.0.0"
}
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.7",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz",
......@@ -1555,6 +1583,27 @@
"node": ">= 8"
}
},
"node_modules/@sideway/address": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz",
"integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==",
"dev": true,
"dependencies": {
"@hapi/hoek": "^9.0.0"
}
},
"node_modules/@sideway/formula": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz",
"integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==",
"dev": true
},
"node_modules/@sideway/pinpoint": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz",
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==",
"dev": true
},
"node_modules/@sqltools/formatter": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz",
......@@ -2198,6 +2247,21 @@
"node": ">=8"
}
},
"node_modules/assertion-error": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
"integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
"dev": true,
"engines": {
"node": "*"
}
},
"node_modules/async": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==",
"dev": true
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
......@@ -2422,6 +2486,24 @@
"node": ">=6"
}
},
"node_modules/chai": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz",
"integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==",
"dev": true,
"dependencies": {
"assertion-error": "^1.1.0",
"check-error": "^1.0.2",
"deep-eql": "^4.1.2",
"get-func-name": "^2.0.0",
"loupe": "^2.3.1",
"pathval": "^1.1.1",
"type-detect": "^4.0.5"
},
"engines": {
"node": ">=4"
}
},
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
......@@ -2456,6 +2538,15 @@
"node": ">=8"
}
},
"node_modules/check-error": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
"integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==",
"dev": true,
"engines": {
"node": "*"
}
},
"node_modules/chokidar": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
......@@ -2491,6 +2582,11 @@
"node": ">=10"
}
},
"node_modules/class-transformer": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz",
"integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw=="
},
"node_modules/class-validator": {
"version": "0.13.2",
"resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.13.2.tgz",
......@@ -2555,6 +2651,18 @@
"node": ">=10"
}
},
"node_modules/cli-progress": {
"version": "3.11.2",
"resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.11.2.tgz",
"integrity": "sha512-lCPoS6ncgX4+rJu5bS3F/iCz17kZ9MPZ6dpuTtI0KXKABkhyXIdYB3Inby1OpaGti3YlI3EeEkM9AuWpelJrVA==",
"dev": true,
"dependencies": {
"string-width": "^4.2.3"
},
"engines": {
"node": ">=4"
}
},
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
......@@ -2694,6 +2802,18 @@
"ms": "^2.1.1"
}
},
"node_modules/deep-eql": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.2.tgz",
"integrity": "sha512-gT18+YW4CcW/DBNTwAmqTtkJh7f9qqScu2qFVlx7kCoeY9tlBu9cUcr7+I+Z/noG8INehS3xQgLpTtd/QUTn4w==",
"dev": true,
"dependencies": {
"type-detect": "^4.0.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
......@@ -2786,6 +2906,21 @@
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
},
"node_modules/ejs": {
"version": "3.1.8",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz",
"integrity": "sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==",
"dev": true,
"dependencies": {
"jake": "^10.8.5"
},
"bin": {
"ejs": "bin/cli.js"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
......@@ -3196,6 +3331,36 @@
"node": "^10.12.0 || >=12.0.0"
}
},
"node_modules/filelist": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
"integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==",
"dev": true,
"dependencies": {
"minimatch": "^5.0.1"
}
},
"node_modules/filelist/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/filelist/node_modules/minimatch": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz",
"integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=10"
}
},
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
......@@ -3351,6 +3516,15 @@
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-func-name": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
"integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==",
"dev": true,
"engines": {
"node": "*"
}
},
"node_modules/get-intrinsic": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz",
......@@ -3697,6 +3871,37 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true
},
"node_modules/jake": {
"version": "10.8.5",
"resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz",
"integrity": "sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==",
"dev": true,
"dependencies": {
"async": "^3.2.3",
"chalk": "^4.0.2",
"filelist": "^1.0.1",
"minimatch": "^3.0.4"
},
"bin": {
"jake": "bin/cli.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/joi": {
"version": "17.7.0",
"resolved": "https://registry.npmjs.org/joi/-/joi-17.7.0.tgz",
"integrity": "sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg==",
"dev": true,
"dependencies": {
"@hapi/hoek": "^9.0.0",
"@hapi/topo": "^5.0.0",
"@sideway/address": "^4.1.3",
"@sideway/formula": "^3.0.0",
"@sideway/pinpoint": "^2.0.0"
}
},
"node_modules/js-sdsl": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz",
......@@ -3764,12 +3969,27 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true
},
"node_modules/loupe": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz",
"integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==",
"dev": true,
"dependencies": {
"get-func-name": "^2.0.0"
}
},
"node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
......@@ -4260,6 +4480,15 @@
"wrappy": "1"
}
},
"node_modules/opencollective-postinstall": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz",
"integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==",
"dev": true,
"bin": {
"opencollective-postinstall": "index.js"
}
},
"node_modules/optional-require": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.8.tgz",
......@@ -4403,6 +4632,15 @@
"node": ">=8"
}
},
"node_modules/pathval": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz",
"integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==",
"dev": true,
"engines": {
"node": "*"
}
},
"node_modules/pg": {
"version": "8.8.0",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.8.0.tgz",
......@@ -5203,6 +5441,15 @@
"node": ">= 0.8.0"
}
},
"node_modules/type-detect": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
"dev": true,
"engines": {
"node": ">=4"
}
},
"node_modules/type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
......@@ -5334,6 +5581,86 @@
}
}
},
"node_modules/typeorm-fixtures-cli": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/typeorm-fixtures-cli/-/typeorm-fixtures-cli-3.0.1.tgz",
"integrity": "sha512-TBP8zECQ7J6+A6+zvFWccwvo58YXwNMay4rKc2UBmS0032BOs+fE9vgdf23q6btnDJN/lklhD3vIt3IM+kcgAw==",
"dev": true,
"hasInstallScript": true,
"dependencies": {
"@faker-js/faker": ">=7.4.0",
"chai": "^4.2.0",
"chalk": "^4.0.0",
"class-transformer": "^0.5.0",
"cli-progress": "^3.10.0",
"ejs": "^3.1.5",
"glob": "^8.0.1",
"joi": "^17.0.0",
"js-yaml": "^4.0.0",
"lodash": "^4.0.0",
"opencollective-postinstall": "^2.0.3",
"reflect-metadata": "^0.1.13",
"resolve-from": "^5.0.0",
"yargs": "^17.5.1"
},
"bin": {
"fixtures": "dist/cli.js",
"fixtures-ts-node-commonjs": "dist/cli-ts-node-commonjs.js",
"fixtures-ts-node-esm": "dist/cli-ts-node-esm.js"
},
"peerDependencies": {
"typeorm": "^0.3.0"
}
},
"node_modules/typeorm-fixtures-cli/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/typeorm-fixtures-cli/node_modules/glob": {
"version": "8.0.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz",
"integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==",
"dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^5.0.1",
"once": "^1.3.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/typeorm-fixtures-cli/node_modules/minimatch": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz",
"integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=10"
}
},
"node_modules/typeorm-fixtures-cli/node_modules/resolve-from": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
"integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/typeorm/node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
......@@ -6920,6 +7247,27 @@
}
}
},
"@faker-js/faker": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-7.6.0.tgz",
"integrity": "sha512-XK6BTq1NDMo9Xqw/YkYyGjSsg44fbNwYRx7QK2CuoQgyy+f1rrTDHoExVM5PsyXCtfl2vs2vVJ0MN0yN6LppRw==",
"dev": true
},
"@hapi/hoek": {
"version": "9.3.0",
"resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.3.0.tgz",
"integrity": "sha512-/c6rf4UJlmHlC9b5BaNvzAcFv7HZ2QHaV0D4/HNlBdvFnvQq8RI4kYdhyPCl7Xj+oWvTWQ8ujhqS53LIgAe6KQ==",
"dev": true
},
"@hapi/topo": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.1.0.tgz",
"integrity": "sha512-foQZKJig7Ob0BMAYBfcJk8d77QtOe7Wo4ox7ff1lQYoNNAb6jwcY1ncdoy2e9wQZzvNy7ODZCYJkK8kzmcAnAg==",
"dev": true,
"requires": {
"@hapi/hoek": "^9.0.0"
}
},
"@humanwhocodes/config-array": {
"version": "0.11.7",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz",
......@@ -7042,6 +7390,27 @@
"fastq": "^1.6.0"
}
},
"@sideway/address": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.4.tgz",
"integrity": "sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==",
"dev": true,
"requires": {
"@hapi/hoek": "^9.0.0"
}
},
"@sideway/formula": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz",
"integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==",
"dev": true
},
"@sideway/pinpoint": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz",
"integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==",
"dev": true
},
"@sqltools/formatter": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.5.tgz",
......@@ -7508,6 +7877,18 @@
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
"dev": true
},
"assertion-error": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
"integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
"dev": true
},
"async": {
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==",
"dev": true
},
"balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
......@@ -7679,6 +8060,21 @@
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"dev": true
},
"chai": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz",
"integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==",
"dev": true,
"requires": {
"assertion-error": "^1.1.0",
"check-error": "^1.0.2",
"deep-eql": "^4.1.2",
"get-func-name": "^2.0.0",
"loupe": "^2.3.1",
"pathval": "^1.1.1",
"type-detect": "^4.0.5"
}
},
"chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
......@@ -7703,6 +8099,12 @@
}
}
},
"check-error": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
"integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==",
"dev": true
},
"chokidar": {
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
......@@ -7724,6 +8126,11 @@
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="
},
"class-transformer": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz",
"integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw=="
},
"class-validator": {
"version": "0.13.2",
"resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.13.2.tgz",
......@@ -7777,6 +8184,15 @@
}
}
},
"cli-progress": {
"version": "3.11.2",
"resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.11.2.tgz",
"integrity": "sha512-lCPoS6ncgX4+rJu5bS3F/iCz17kZ9MPZ6dpuTtI0KXKABkhyXIdYB3Inby1OpaGti3YlI3EeEkM9AuWpelJrVA==",
"dev": true,
"requires": {
"string-width": "^4.2.3"
}
},
"cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
......@@ -7885,6 +8301,15 @@
"ms": "^2.1.1"
}
},
"deep-eql": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.2.tgz",
"integrity": "sha512-gT18+YW4CcW/DBNTwAmqTtkJh7f9qqScu2qFVlx7kCoeY9tlBu9cUcr7+I+Z/noG8INehS3xQgLpTtd/QUTn4w==",
"dev": true,
"requires": {
"type-detect": "^4.0.0"
}
},
"deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
......@@ -7952,6 +8377,15 @@
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="
},
"ejs": {
"version": "3.1.8",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.8.tgz",
"integrity": "sha512-/sXZeMlhS0ArkfX2Aw780gJzXSMPnKjtspYZv+f3NiKLlubezAHDU5+9xz6gd3/NhG3txQCo6xlglmTS+oTGEQ==",
"dev": true,
"requires": {
"jake": "^10.8.5"
}
},
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
......@@ -8273,6 +8707,35 @@
"flat-cache": "^3.0.4"
}
},
"filelist": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz",
"integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==",
"dev": true,
"requires": {
"minimatch": "^5.0.1"
},
"dependencies": {
"brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0"
}
},
"minimatch": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz",
"integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==",
"dev": true,
"requires": {
"brace-expansion": "^2.0.1"
}
}
}
},
"fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
......@@ -8393,6 +8856,12 @@
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
},
"get-func-name": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
"integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==",
"dev": true
},
"get-intrinsic": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz",
......@@ -8638,6 +9107,31 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true
},
"jake": {
"version": "10.8.5",
"resolved": "https://registry.npmjs.org/jake/-/jake-10.8.5.tgz",
"integrity": "sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==",
"dev": true,
"requires": {
"async": "^3.2.3",
"chalk": "^4.0.2",
"filelist": "^1.0.1",
"minimatch": "^3.0.4"
}
},
"joi": {
"version": "17.7.0",
"resolved": "https://registry.npmjs.org/joi/-/joi-17.7.0.tgz",
"integrity": "sha512-1/ugc8djfn93rTE3WRKdCzGGt/EtiYKxITMO4Wiv6q5JL1gl9ePt4kBsl1S499nbosspfctIQTpYIhSmHA3WAg==",
"dev": true,
"requires": {
"@hapi/hoek": "^9.0.0",
"@hapi/topo": "^5.0.0",
"@sideway/address": "^4.1.3",
"@sideway/formula": "^3.0.0",
"@sideway/pinpoint": "^2.0.0"
}
},
"js-sdsl": {
"version": "4.1.5",
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz",
......@@ -8693,12 +9187,27 @@
"p-locate": "^5.0.0"
}
},
"lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
"lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true
},
"loupe": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz",
"integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==",
"dev": true,
"requires": {
"get-func-name": "^2.0.0"
}
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
......@@ -9043,6 +9552,12 @@
"wrappy": "1"
}
},
"opencollective-postinstall": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz",
"integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==",
"dev": true
},
"optional-require": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.8.tgz",
......@@ -9152,6 +9667,12 @@
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
"dev": true
},
"pathval": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz",
"integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==",
"dev": true
},
"pg": {
"version": "8.8.0",
"resolved": "https://registry.npmjs.org/pg/-/pg-8.8.0.tgz",
......@@ -9716,6 +10237,12 @@
"prelude-ls": "^1.2.1"
}
},
"type-detect": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
"dev": true
},
"type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
......@@ -9784,6 +10311,67 @@
}
}
},
"typeorm-fixtures-cli": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/typeorm-fixtures-cli/-/typeorm-fixtures-cli-3.0.1.tgz",
"integrity": "sha512-TBP8zECQ7J6+A6+zvFWccwvo58YXwNMay4rKc2UBmS0032BOs+fE9vgdf23q6btnDJN/lklhD3vIt3IM+kcgAw==",
"dev": true,
"requires": {
"@faker-js/faker": ">=7.4.0",
"chai": "^4.2.0",
"chalk": "^4.0.0",
"class-transformer": "^0.5.0",
"cli-progress": "^3.10.0",
"ejs": "^3.1.5",
"glob": "^8.0.1",
"joi": "^17.0.0",
"js-yaml": "^4.0.0",
"lodash": "^4.0.0",
"opencollective-postinstall": "^2.0.3",
"reflect-metadata": "^0.1.13",
"resolve-from": "^5.0.0",
"yargs": "^17.5.1"
},
"dependencies": {
"brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0"
}
},
"glob": {
"version": "8.0.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-8.0.3.tgz",
"integrity": "sha512-ull455NHSHI/Y1FqGaaYFaLGkNMMJbavMrEGFXG/PGrg6y7sutWHUHrz6gy6WEBH6akM1M414dWKCNs+IhKdiQ==",
"dev": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^5.0.1",
"once": "^1.3.0"
}
},
"minimatch": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.0.tgz",
"integrity": "sha512-9TPBGGak4nHfGZsPBohm9AWg6NoT7QTCehS3BIJABslyZbzxfV78QM2Y6+i741OPZIafFAaiiEMh5OyIrJPgtg==",
"dev": true,
"requires": {
"brace-expansion": "^2.0.1"
}
},
"resolve-from": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
"integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
"dev": true
}
}
},
"typescript": {
"version": "4.8.4",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz",
......
......@@ -7,18 +7,21 @@
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "nodemon",
"start": "npm run build && node build/server.js",
"lint": "eslint . --ext .ts"
"lint": "eslint . --ext .ts",
"fixtures": "ts-node ./src/fixtures.ts"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@faker-js/faker": "^7.6.0",
"@types/node": "^18.11.8",
"@typescript-eslint/eslint-plugin": "^5.41.0",
"@typescript-eslint/parser": "^5.41.0",
"eslint": "^8.26.0",
"nodemon": "^2.0.20",
"ts-node": "^10.9.1",
"typeorm-fixtures-cli": "^3.0.1",
"typescript": "^4.8.4"
},
"dependencies": {
......@@ -26,6 +29,7 @@
"@types/cors": "^2.8.12",
"@types/express": "^4.17.14",
"bcrypt": "^5.1.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.13.2",
"cors": "^2.8.5",
"express": "^4.18.2",
......
import { myDataSource } from "./app-data-source";
import { User, UserRole } from "./models/User";
import { faker } from '@faker-js/faker';
import { Task } from "./models/Task";
import { Project } from "./models/Project";
function randomIntFromInterval(min:number, max:number) {
return Math.floor(Math.random() * (max - min + 1) + min)
}
const loadFixtures = async () => {
myDataSource
.initialize()
.then(async () => {
const repositoryTask = myDataSource.getRepository(Task);
await repositoryTask.delete({});
const repositoryUser = myDataSource.getRepository(User);
await repositoryUser.delete({});
const repositoryProject = myDataSource.getRepository(Project);
await repositoryProject.delete({});
console.log('========================== ' + '\n' + 'Data Source has been cleared!' +'\n' + '==========================')
const userRoles = [{role: UserRole.DIRECTOR}, {role: UserRole.SUPERUSER}, {role: UserRole.USER}, {role: UserRole.USER}];
const users = []
for (let i = 0; i < 4; i++) {
const name = faker.name.firstName()
const surname = faker.name.lastName()
const displayName = name + ' ' + surname[0] + '.'
const user = new User()
user.name = name;
user.surname = surname;
user.password = '12345qwert';
user.displayName= displayName;
user.phone = faker.phone.number('+77#########')
user.email = faker.internet.email();
user.role = userRoles[i].role;
user.generateToken()
await user.save();
users.push(user)
}
const tasks = []
type taskFinishType = "opened" | "done" |"failed";
type priorityType = "A" | "B" |"C";
const priorities:priorityType[] = ["A", "B" , "C"]
const accomplish:taskFinishType[] = ["opened", "done" , "failed"]
for (let i = 0; i < 15; i++) {
if (i <= 10) {
const newTask = new Task();
newTask.title = `Buy ${faker.commerce.productName()}`;
newTask.description = faker.random.words(4);
newTask.executors = faker.helpers.arrayElements(users, randomIntFromInterval(0, 3));
newTask.dateTimeDue = faker.date.soon(randomIntFromInterval(1, 15));
newTask.dateTimeStart = faker.date.recent((randomIntFromInterval(0, 8)));
newTask.author = faker.helpers.arrayElement(users);
newTask.accomplish = faker.helpers.arrayElement(accomplish);
newTask.priority = faker.helpers.arrayElement(priorities);
await newTask.save();
tasks.push(newTask)
} else {
const newTask = new Task();
newTask.title = `Buy ${faker.commerce.productName()}`;
newTask.description = faker.random.words(4);
newTask.executors = faker.helpers.arrayElements(users, randomIntFromInterval(0, 3));
newTask.dateTimeDue = null;
newTask.dateTimeStart = null;
newTask.author = faker.helpers.arrayElement(users);
newTask.accomplish = accomplish[0];
newTask.priority = faker.helpers.arrayElement(priorities);
await newTask.save();
tasks.push(newTask)
}
}
console.log('========================== ' + '\n' + 'Fixtures done!' +'\n' + '==========================')
})
.catch((err) => {
console.error("Error during Data Source initialization:", err)
})
};
loadFixtures()
\ No newline at end of file
......@@ -11,7 +11,8 @@ import {
import {User} from './User';
import {Project} from './Project';
type taskFinishType = "open" | "done" |"failed";
type taskFinishType = "opened" | "done" |"failed";
type priorityType = "A" | "B" |"C";
interface ITask{
id: string;
......@@ -21,6 +22,7 @@ import {
dateTimeStart:Date| null;
dateTimeDue:Date| null;
accomplish: taskFinishType;
priority: priorityType;
author: User;
project:Project|null;
executors:User[]
......@@ -40,6 +42,7 @@ import {
dateTimeStart!: Date | null;
@Column({ name: 'dateTimeDue', type: Date,nullable: true })
dateTimeDue!: Date | null;
@Column({
type: "enum",
enum: ["opened", "done" , "failed"],
......@@ -48,6 +51,15 @@ import {
accomplish!: taskFinishType
@Column({
type: "enum",
enum: ["A", "B" , "C"],
default: "C"
})
priority!: priorityType
@ManyToOne(() => User, (user: { tasks: Task[]; }) => user.tasks,{eager : true})
author!: User;
......
......@@ -7,11 +7,11 @@ import {
BaseEntity,
ManyToMany,
OneToMany,
JoinTable
JoinTable,
} from 'typeorm';
import {IsEmail
} from "class-validator";
import { Exclude, instanceToPlain } from "class-transformer";
import bcrypt from 'bcrypt';
import {nanoid} from 'nanoid';
import {Task} from './Task';
......@@ -20,17 +20,17 @@ import {Project} from './Project';
const SALT_WORK_FACTOR= 10;
type userRoleType = "worker" | "director";
export enum UserRole {USER="user" ,DIRECTOR= "director",SUPERUSER="superuser"}
interface IUser {
id:string;
role: UserRole;
name: string;
surname: string;
email: string;
displayName: string;
password:string;
token: string;
role: userRoleType;
createdAt: Date;
createdTasks:Task[];
workerInProjects:Project[];
......@@ -41,39 +41,40 @@ interface IUser {
@Entity({ name: 'User' })
export class User extends BaseEntity implements IUser {
@PrimaryGeneratedColumn('uuid')
id!: string
@Column({ name: 'name', type: 'varchar', length:20,nullable: false })
name!: string
@PrimaryGeneratedColumn("uuid")
id!: string;
@Column({ name: 'name', type: 'varchar', length:30,nullable: false })
name!: string;
@Column({ name: 'surname', type: 'varchar', length:30,nullable: false })
surname!: string
surname!: string;
@Column({ name: 'displayName', type: 'varchar', length:30,nullable: false })
displayName!: string
@Column({ name: 'displayName', type: 'varchar', length:35,nullable: false })
displayName!: string;
@Column({ name: 'email', type: 'varchar',length:20, unique: true, nullable: false })
@Column({ name: 'email', type: 'varchar',length:40, unique: true, nullable: false })
@IsEmail()
email!: string
email!: string;
@Column({ name: 'phone', type: 'varchar',length:10, unique: true, nullable: true})
phone?: string
@Column({ name: 'phone', type: 'varchar',length:15, unique: true, nullable: true})
phone?: string;
@Column({ name: 'token', type: 'varchar',length:100, unique: true, nullable: false })
token!: string
token!: string;
@CreateDateColumn({ name: 'created_at', type: Date, default: new Date() })
createdAt!: Date;
@Column({
type: "enum",
enum: ["worker", "director"],
default: "worker"
enum: UserRole,
default: UserRole.USER
})
role!: userRoleType
role!: UserRole
@Column({ type: 'varchar', nullable: false, select:false })
password!: string
@Column({ type: 'varchar', nullable: false, select:true })
@Exclude({ toPlainOnly: true })
password!: string;
@OneToMany(() => Task, (task: { user: User }) => task.user)
......@@ -106,20 +107,16 @@ export class User extends BaseEntity implements IUser {
public async checkPassword(
candidatePassword: string,
):Promise<boolean> {
console.log("Checking password", candidatePassword,'this.password', this.password)
return await bcrypt.compare(candidatePassword, this.password);
}
toJSON() {
return instanceToPlain(this);
}
}
......
......@@ -17,7 +17,7 @@ export default router;
router.post('/', async(req:Request, res:Response):Promise<Response>=>{
const token = req.get('Authorization');
const newTask = new Task();
const {title,description,project,executors,dateTimeDue,dateTimeStart} = req.body;
const {title,description,project,executors,dateTimeDue,dateTimeStart,accomplish,priority} = req.body;
const user = await dataSource
.createQueryBuilder()
.select("user")
......@@ -32,11 +32,13 @@ router.post('/', async(req:Request, res:Response):Promise<Response>=>{
newTask.dateTimeDue = dateTimeDue;
newTask.dateTimeStart = dateTimeStart;
newTask.author= user;
newTask.accomplish = accomplish;
newTask.priority = priority;
await newTask.save();
return res.send({newTask})
})
router.get('/userId/:userId', async (req: Request, res: Response)=>{
router.get('/userId/:userId', async (req: Request, res: Response):Promise<Response>=>{
const userId = req.params.userId;
const tasks = await dataSource
.getRepository(Task)
......@@ -47,8 +49,7 @@ router.get('/userId/:userId', async (req: Request, res: Response)=>{
return res.send({tasks})
})
router.get('/my', async (req: Request, res: Response)=>{
router.get('/my', async (req: Request, res: Response):Promise<Response>=>{
const token = req.get('Authorization');
const user = await dataSource
.createQueryBuilder()
......@@ -65,3 +66,55 @@ router.get('/my', async (req: Request, res: Response)=>{
.getMany()
return res.send({tasks})
})
router.delete('/:taskId',async (req: Request, res: Response):Promise<Response>=>{
// const token = req.get('Authorization');
// const user = await dataSource
// .createQueryBuilder()
// .select("user")
// .from(User, "user")
// .where("user.token = :token", { token: token })
// .getOne()
// if(!user) return res.status(404).send({Message:'user not found'})
const taskId = req.params.taskId;
await myDataSource
.createQueryBuilder()
.delete()
.from(Task)
.where("id = :id", { id: taskId })
.execute()
return res.send({message: 'Task deleted successfully'})
})
router.put('/',async(req:Request, res:Response)=> {
const token = req.get('Authorization');
const user = await dataSource
.createQueryBuilder()
.select("user")
.from(User, "user")
.where("user.token = :token", { token: token })
.getOne()
if (!user) return res.status(404).send({Message:'user not found'})
const {id,title,description,project,executors,dateTimeDue,dateTimeStart,accomplish,priority} = req.body;
await dataSource
.createQueryBuilder()
.update(Task)
.set({
title: title,
description: description,
project: project,
executors: executors,
dateTimeDue: dateTimeDue,
dateTimeStart: dateTimeStart,
author:user,
accomplish: accomplish,
priority: priority
})
.where("id = :id", { id: id })
.execute()
res.send({message:'update task successfully'})
// res.send({task})
})
......@@ -17,7 +17,7 @@ return res.send({users})
router.post('/', async (req : Request, res : Response):Promise<object> => {
const {name,surname,password,email} = req.body;
const {name,surname,password,email, role} = req.body;
const displayName = surname+' '+name[0]+'.'
const user = new User();
user.name = name;
......@@ -25,6 +25,7 @@ router.post('/', async (req : Request, res : Response):Promise<object> => {
user.password = password;
user.displayName= displayName;
user.email = email;
user.role = role;
user.generateToken()
await user.save();
const userToFront:User|null = await dataSource.manager.findOneBy(User, {
......@@ -42,9 +43,9 @@ router.post('/sessions/', async (req : Request, res : Response):Promise<object>
.select("user")
.from(User, "user")
.where("user.email = :email", { email: email })
.addSelect('password')
.getOne()
if(!user) return res.status(404).send({Message:'user not found'})
const isMatch:boolean = await user.checkPassword(password);
console.log("123")
if (!isMatch) return res.status(400).send({
......@@ -69,7 +70,6 @@ router.delete('/sessions', async(req: Request, res: Response):Promise<void | obj
token: token
})
if(!user) return res.send({successMsg});
console.log('token: ' + token)
user.token = nanoid();
await user.save();
......
import { Grid } from "@mui/material";
const CalendarRow = ({children}) => {
return <>
<Grid
container
align='center'
sx={{borderBottom: '1px solid black', borderRight: '1px solid black', borderLeft: '1px solid black'}}
>
{children}
</Grid>
</>
};
export default CalendarRow;
import { Grid } from "@mui/material";
const CalendarSmallCell = ({children, xs}) => {
return <>
<Grid align='center' item xs={xs} sx={{borderRight: '1px solid black'}}>
{children}
</Grid>
</>
};
export default CalendarSmallCell;
\ No newline at end of file
import { Grid } from "@mui/material";
const CalendarStandartCell = ({children, xs, onClick}) => {
return <>
<Grid
item xs={xs}
sx={{borderRight: '1px solid black'}}
onClick={onClick}>
{children}
</Grid>
</>
};
export default CalendarStandartCell;
\ No newline at end of file
import { Grid, TextField } from "@mui/material";
import React, { useEffect, useState } from "react";
const CalendarTask = ({year, month, tasks, day, hours, setCurrentTask, onChange, hourFormat}) => {
const getTaskInDayCell = (tasks, day, hours) => {
const hour = parseInt(hours.split(':')[0])
let hourDiffEnd
let hourDiffStart
if (hourFormat) {
hourDiffEnd = hour + 1
} else {
hourDiffEnd = hour + 2
}
if (hourFormat) {
hourDiffStart = hour - 1
} else {
hourDiffStart = hour - 2
}
const tasksCell = tasks.filter(task=> {
if (year === task.infoForCell.startYear) {
if (month + 1 === task.infoForCell.startMonth) {
if (day.dayNumber === task.infoForCell.startDay) {
if (((task.infoForCell.endHour <= hour || task.infoForCell.startHour <= hour) && (task.infoForCell.endHour > hour))
|| (task.infoForCell.startHour >= hour && task.infoForCell.endHour < hourDiffEnd)
|| (task.infoForCell.endMinute <= 59 && task.infoForCell.endHour === hour)) {
return task
}
}
}
}
})
return tasksCell
}
const tasksCell = getTaskInDayCell(tasks, day, hours)
return (<>
{tasksCell.length ? tasksCell.map((task, i)=>
{
return (
<Grid key={task.id} sx={{backgroundColor: 'lightgreen'}}>
<TextField
id={task.title}
variant="standard"
value={task.title}
name='title'
onClick={(e)=>{e.stopPropagation(); setCurrentTask(task)}}
onChange={onChange}>
</TextField>
</Grid>)}
) : null }
</>)
};
export default CalendarTask;
\ No newline at end of file
import { Grid, TextField } from "@mui/material";
import { FormControlLabel, Switch } from "@mui/material";
import { useEffect, useState } from "react";
import CalendarRow from "./CalendarRow/CalendarRow";
import CalendarSmallCell from "./CalendarSmallCell/CalendarSmallCell";
import CalendarStandartCell from "./CalendarStandartCell.js/CalendarStandartCell";
import CalendarTask from "./CalendarTask/CalendarTask";
const exampleTasks=[
{
user:"first",
title:"задача1",
description:"описание задачи11111",
priority:"A",
author:"Ivan",
executor:"Arman",
dateTimeStart:"26.12.2022 20:00:00",
dateTimeDue:"27.10.2022 14:20:00",
id:1,
dateCreated:"26.10.2022"
}, {
user:"first",
title:"задача2",
description:"описание задачи11111",
priority:"A",
author:"Ivan",
executor:"Arman",
dateTimeStart:"1.12.2022 9:00:00",
dateTimeDue:"27.10.2022 15:20:00",
id:1,
dateCreated:"26.10.2022"
},{
user:"first",
title:"first",
description:"описание задачи11111",
priority:"A",
author:"Ivan",
executor:"Arman",
dateTimeStart:"5.11.2022 16:00:00",
dateTimeDue:"5.11.2022 17:00:00",
id:1,
dateCreated:"26.10.2022"
},{
user:"first",
title:"second",
description:"описание задачи11111",
priority:"A",
author:"Ivan",
executor:"Arman",
dateTimeStart:"5.11.2022 16:00:00",
dateTimeDue:"5.11.2022 17:00:00",
id:1,
dateCreated:"26.10.2022"
}
]
function MonthCalendarBody({month, year, tasks, createTaskInCellHandler, onChangeCellTaskTitle, setCurrentTask, hourFormat, setHourFormat}) {
function MonthCalendarBody({month, year}) {
const [hoursInDay, setHoursInDay] = useState(['8:00', '10:00', '12:00', '14:00', '16:00', '18:00', '20:00', '22:00', '6:00'])
const [hoursInDay, setHoursInDay] = useState(['8:00', '10:00', '12:00', '14:00', '16:00', '18:00', '20:00', '22:00'])
const [daysInMonth, setDaysInMonth] = useState([])
const [tasksForCell, setTasksForCell] = useState([])
const [cellSizes, setCellSizes] = useState({})
useEffect(()=>{
const cells = hoursInDay.length
const xs = 10.8/cells
setCellSizes(()=>{
return {smallCell: 0.6, standarCell: xs}
})
}, [])
useEffect(()=>{
setNewMonthDays(month, year)
setNewTasksWithInfoForCell(exampleTasks, month, year)
}, [month, year])
if (hourFormat) {
const arr = ['8:00', '9:00', '10:00', '11:00', '12:00', '13:00', '14:00', '15:00', '16:00', '17:00', '18:00', '19:00', '20:00','21:00','22:00']
const cells = arr.length
const xs = 10.8/cells
setCellSizes(()=>{
return {smallCell: 0.6, standarCell: xs}
})
setHoursInDay(()=>arr)
} else {
const arr = ['8:00', '10:00', '12:00', '14:00', '16:00', '18:00', '20:00', '22:00']
const cells = arr.length
const xs = 10.8/cells
setCellSizes(()=>{
return {smallCell: 0.6, standarCell: xs}
})
setHoursInDay(()=>arr)
}
}, [hourFormat])
useEffect(()=>{
setNewMonthDays()
}, [month])
const getDaysInMonth = () => {
return new Date(year, month + 1, 0).getDate();
......@@ -69,147 +50,66 @@ function MonthCalendarBody({month, year}) {
return ["ВС","ПН","ВТ","СР","ЧТ","ПТ","СБ"][day];
}
const getDayOfWeekNumber = (month, year, day) => {
const getDayOfWeekNumber = (day) => {
return new Date(year, month, day).getDay()
}
const setNewTasksWithInfoForCell = (tasks, month, year) => {
const newArr = tasks.map((task)=>{
const dateStart = task.dateTimeStart.split(' ')[0]
const timeStart = task.dateTimeStart.split(' ')[1]
const timeEnd = task.dateTimeDue.split(' ')[1]
const dayStart = parseInt(dateStart.split('.')[0])
const dayOfWeekStartNumber = getDayOfWeekNumber(month, year, dayStart)
const dayOfWeekStartString = getDayOfWeekString(dayOfWeekStartNumber)
const monthStartNumber = parseInt(dateStart.split('.')[1])
const yearStartNumber = parseInt(dateStart.split('.')[2])
const timeStartHour = parseInt(timeStart.split(':')[0])
const timeEndHour = parseInt(timeEnd.split(':')[0])
return {...task,
startDay: dayStart,
startDayOfWeek: dayOfWeekStartString,
startHour: timeStartHour,
startMonth: monthStartNumber,
startYear: yearStartNumber,
endHour: timeEndHour,
}
})
setTasksForCell(newArr)
}
const createTaskInCellHandler = (month, year, dayOfWeek, dayNumber, dayHour) => {
const newTasks = [...tasksForCell]
const newTask = {
id: Date.now(),
user:"first",
title:"Новая",
description:"описание задачи11111",
priority:"A",
author:"Ivan",
executor:"Arman",
dateTimeStart:`${dayNumber}.${month+1}.${year} ${parseInt(dayHour.split(':')[0])}:00:00`,
dateTimeDue:`${dayNumber}.${month+1}.${year} ${parseInt(dayHour.split(':')[0]) + 1}:00:00`,
}
newTasks.push(newTask)
exampleTasks.push(newTask)
setNewTasksWithInfoForCell(newTasks, month, year)
}
const getTaskInDayCell = (tasks, day, hours, month, year) => {
const task = tasks.find(task=> {
if (year === task.startYear) {
if (month + 1 === task.startMonth) {
if (day.dayNumber === task.startDay && task.startDayOfWeek === day.dayOfWeek ) {
if ((task.endHour <= parseInt(hours.split(':')[0]) || task.startHour <= parseInt(hours.split(':')[0])) && (task.endHour >= parseInt(hours.split(':')[0]))) {
return task
}
}
}
}
})
return task
}
const setNewMonthDays = (month, year) => {
const setNewMonthDays = () => {
const newDaysInMonth = []
for (let i = 1; i <= getDaysInMonth(month, year); i++) {
const dayOfWeekNumber = getDayOfWeekNumber(month, year, i)
for (let i = 1; i <= getDaysInMonth(); i++) {
const dayOfWeekNumber = getDayOfWeekNumber(i)
newDaysInMonth.push({dayNumber: i, dayOfWeek: getDayOfWeekString(dayOfWeekNumber)})
}
setDaysInMonth(newDaysInMonth)
}
const onChangeCellTaskTitle = (e, task) => {
const value = e.target.value;
const name = e.target.name;
const { id } = task;
const newTasks = tasksForCell.map(task => {
if (task.id === id) {
return { ...task, [name]: value };
}
return task;
});
setTasksForCell(newTasks);
exampleTasks = [...newTasks]
setDaysInMonth(prevState=>newDaysInMonth)
}
return (
<>
<Grid
container
align='center'
sx={{borderBottom: '1px solid black', borderRight: '1px solid black', borderLeft: '1px solid black'}}
<CalendarRow
>
<Grid align='center' item xs={0.5} sx={{borderRight: '1px solid black'}}>
{' '}
</Grid>
<Grid align='center' item xs={0.5} sx={{borderRight: '1px solid black'}}>
{' '}
</Grid>
<CalendarSmallCell xs={1.2}>
<FormControlLabel
control={<Switch color="primary" checked={hourFormat} onChange={()=>{setHourFormat(()=>!hourFormat)}}/>}
label="1 час"
labelPlacement="end"
/>
</CalendarSmallCell>
{hoursInDay.map((hours, i)=>{
return (
<Grid key={i} item xs={1.2222} sx={{borderRight: '1px solid black'}}>
<CalendarStandartCell key={i} xs={cellSizes.standarCell}>
{hours}
</Grid>
</CalendarStandartCell>
)
})}
</Grid>
</CalendarRow>
{daysInMonth.map((day, i)=>{
return (
<Grid
<CalendarRow
key={i}
container
align='center'
sx={{borderBottom: '1px solid black', borderRight: '1px solid black', borderLeft: '1px solid black'}}
>
<Grid align='center' item xs={0.5} sx={{borderRight: '1px solid black'}}>
{day.dayNumber}
</Grid>
<Grid align='center' item xs={0.5} sx={{borderRight: '1px solid black'}}>
{day.dayOfWeek}
</Grid>
<CalendarSmallCell xs={cellSizes.smallCell}>{day.dayNumber}</CalendarSmallCell>
<CalendarSmallCell xs={cellSizes.smallCell}>{day.dayOfWeek}</CalendarSmallCell>
{hoursInDay.map((hours, i)=>{
const task = getTaskInDayCell(tasksForCell, day, hours, month, year)
return (
<Grid
<CalendarStandartCell
key={i}
item xs={1.2222}
sx={{borderRight: '1px solid black'}}
onClick={()=>{createTaskInCellHandler(month, year, day.dayOfWeek, day.dayNumber, hours)}}>
{ task ?
<Grid key={i} sx={{backgroundColor: 'lightgreen'}}>
<TextField
id={task.title}
variant="standard"
value={task.title}
name='title'
onChange={(e)=>{onChangeCellTaskTitle(e, task)}}></TextField>
</Grid> : null}
</Grid>
item xs={cellSizes.standarCell}
onClick={()=>{createTaskInCellHandler(day.dayNumber, hours)}}
>
<CalendarTask
setCurrentTask={setCurrentTask}
onChange={(e)=>{onChangeCellTaskTitle(e)}}
year={year}
month={month}
tasks={tasks}
day={day}
hours={hours}
hourFormat={hourFormat}
/>
</CalendarStandartCell>
)
})}
</Grid>
</CalendarRow>
)
})}
</>
......
import { Container } from '@mui/material';
import { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import MonthCalendarBody from '../../components/MonthCalendarBody/MonthCalendarBody';
import MonthCalendarHeader from '../../components/MonthCalendarHeader/MonthCalendarHeader';
import { addTask, fetchTasks} from '../../store/actions/tasksActions';
function MonthCalendar() {
const dispatch = useDispatch();
const { tasks } = useSelector(state => state.tasks);
const [hourFormat, setHourFormat] = useState(false);
const [month, setMonth] = useState('')
const [year, setYear] = useState('')
const [worker, setWorker] = useState('');
const [calendarType, setCalendarType] = useState('Месяц');
const [currentTask, setCurrentTask] = useState({})
useEffect(()=>{
setMonth(new Date().getMonth())
setYear(new Date().getFullYear())
},[])
dispatch(fetchTasks())
}, [dispatch])
const onChangeWorkerHandler = (event) => {
setWorker(event.target.value);
};
......@@ -46,6 +54,40 @@ function MonthCalendar() {
})
}
function dateToISOLikeButLocal(date) {
const offsetMs = date.getTimezoneOffset() * 60 * 1000;
const msLocal = date.getTime() - offsetMs;
const dateLocal = new Date(msLocal);
const iso = dateLocal.toISOString();
return iso;
}
const createTaskInCellHandler = (dayNumber, dayHour) => {
const hour = parseInt(dayHour.split(':')[0])
let hourDue
if (hourFormat) {
hourDue = hour + 0
} else {
hourDue = hour + 1
}
const newTask = {
title:"Задача",
description:"описание",
dateTimeStart: dateToISOLikeButLocal(new Date(year, month, dayNumber, hour, 0)),
dateTimeDue: dateToISOLikeButLocal(new Date(year, month, dayNumber, hourDue, 59)),
}
console.log(newTask)
dispatch(addTask(newTask))
setCurrentTask((newTask))
}
const onChangeCellTaskTitle = (e) => {
e.stopPropagation()
const value = e.target.value;
const name = e.target.name;
setCurrentTask({ ...currentTask, [name]: value })
}
return (
<>
<Container>
......@@ -63,6 +105,12 @@ function MonthCalendar() {
<MonthCalendarBody
month={month}
year={year}
tasks={tasks}
createTaskInCellHandler={createTaskInCellHandler}
onChangeCellTaskTitle={onChangeCellTaskTitle}
setCurrentTask={setCurrentTask}
hourFormat={hourFormat}
setHourFormat={setHourFormat}
/>
</Container>
</>
......
......@@ -5,6 +5,7 @@ import App from './App';
import { configureStore } from '@reduxjs/toolkit';
import { Provider } from 'react-redux';
import usersReducer from './store/reducers/usersReducer';
import tasksReducer from './store/reducers/tasksReducer';
import axios from 'axios';
const localStorageMiddleware = ({getState}) => (next) => (action) => {
......@@ -31,6 +32,7 @@ axios.interceptors.request.use(config=>{
const store = configureStore({
reducer: {
users: usersReducer,
tasks: tasksReducer
},
preloadedState: loadFromLocalStorage(),
middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(localStorageMiddleware)
......
export const FETCH_TASKS_REQUEST = "FETCH_TASKS_REQUEST";
export const FETCH_TASKS_SUCCESS = "FETCH_TASKS_SUCCESS";
export const FETCH_TASKS_FAILURE = "FETCH_TASKS_FAILURE";
export const ADD_NEW_TASK_REQUEST = "ADD_NEW_TASK_REQUEST";
export const ADD_NEW_TASK_SUCCESS = "ADD_NEW_TASK_SUCCESS";
export const ADD_NEW_TASK_FAILURE = "ADD_NEW_TASK_FAILURE";
\ No newline at end of file
import { ADD_NEW_TASK_FAILURE, ADD_NEW_TASK_REQUEST, ADD_NEW_TASK_SUCCESS, FETCH_TASKS_FAILURE, FETCH_TASKS_REQUEST, FETCH_TASKS_SUCCESS} from "../actionTypes/tasksTypes";
import axios from '../../axiosPlanner'
const fetchTasksRequest = () => {
return {type: FETCH_TASKS_REQUEST}
};
const fetchTasksSuccess = (tasks) => {
return {type: FETCH_TASKS_SUCCESS, tasks}
};
const fetchTasksFailure = (error) => {
return {type: FETCH_TASKS_FAILURE, error}
};
export const fetchTasks = () => {
return async (dispatch) => {
dispatch(fetchTasksRequest());
try {
const response = await axios.get("/tasks");
dispatch(fetchTasksSuccess(response.data.tasks))
} catch (error) {
dispatch(fetchTasksFailure(error.response.data));
}
}
}
const addTaskRequest = () => {
return {type: ADD_NEW_TASK_REQUEST}
};
const addTaskSuccess = () => {
return {type: ADD_NEW_TASK_SUCCESS}
};
const addTaskFailure = (error) => {
return {type: ADD_NEW_TASK_FAILURE, error}
};
export const addTask = (task) => {
return async (dispatch, getState) => {
dispatch(addTaskRequest());
const token = getState().users?.user?.token;
try {
await axios.post("/tasks", task, {
headers: {
'Authorization': 'yjBjcPCQwytwrYo9rRuiK'
}
});
dispatch(addTaskSuccess())
dispatch(fetchTasks())
} catch (error) {
dispatch(addTaskFailure(error.response.data));
}
}
}
\ No newline at end of file
import { FETCH_TASKS_FAILURE, FETCH_TASKS_REQUEST, FETCH_TASKS_SUCCESS} from "../actionTypes/tasksTypes";
const initialState = {
tasks: [],
loading: false,
error: null
};
const tasksReduсer = (state = initialState, action) => {
switch(action.type) {
case FETCH_TASKS_REQUEST:
return {...state, loading: true};
case FETCH_TASKS_SUCCESS:
const newArr = []
action.tasks.forEach((task)=>{
if (task.dateTimeStart && task.dateTimeDue) {
if (new Date(task.dateTimeDue).getTime() - new Date(task.dateTimeStart).getTime() < (4 * 3600000)) {
const dateStart = task.dateTimeStart.split('T')[0]
const timeStart = task.dateTimeStart.split('T')[1]
const timeEnd = task.dateTimeDue.split('T')[1]
const dayStart = parseInt(dateStart.split('-')[2])
const monthStartNumber = parseInt(dateStart.split('-')[1])
const yearStartNumber = parseInt(dateStart.split('-')[0])
const timeStartHour = parseInt(timeStart.split(':')[0])
const timeEndHour = parseInt(timeEnd.split(':')[0])
const timeStartMinute = parseInt(timeStart.split(':')[1])
const timeEndMinute = parseInt(timeEnd.split(':')[1])
newArr.push({...task, infoForCell: {
startDay: dayStart,
startHour: timeStartHour,
startMonth: monthStartNumber,
startYear: yearStartNumber,
startMinute: timeStartMinute,
endHour: timeEndHour,
endMinute: timeEndMinute,
}
} )
}
}
})
return {...state, loading: false, tasks: newArr};
case FETCH_TASKS_FAILURE:
return {...state, loading: false, error: action.error};
default:
return state;
}
};
export default tasksReduсer;
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment