diff --git a/package-lock.json b/package-lock.json index a381a4ba..b69d5e4b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2624,6 +2624,11 @@ "@babel/types": "^7.3.0" } }, + "@types/bcrypt": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-3.0.0.tgz", + "integrity": "sha512-nohgNyv+1ViVcubKBh0+XiNJ3dO8nYu///9aJ4cgSqv70gBL+94SNy/iC2NLzKPT2Zt/QavrOkBVbZRLZmw6NQ==" + }, "@types/bluebird": { "version": "3.5.27", "resolved": "https://registry.npmjs.org/@types/bluebird/-/bluebird-3.5.27.tgz", @@ -3135,12 +3140,26 @@ "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", "integrity": "sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY=" }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==" + }, "archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", "dev": true }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -3492,6 +3511,22 @@ "safe-buffer": "5.1.2" } }, + "bcrypt": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-3.0.6.tgz", + "integrity": "sha512-taA5bCTfXe7FUjKroKky9EXpdhkVvhE5owfxfLYodbrAR1Ul3juLmIQmIQBK4L9a5BuUcE6cqmwT+Da20lF9tg==", + "requires": { + "nan": "2.13.2", + "node-pre-gyp": "0.12.0" + }, + "dependencies": { + "nan": { + "version": "2.13.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.13.2.tgz", + "integrity": "sha512-TghvYc72wlMGMVMluVo9WRJc0mB8KxxF/gZ4YYFy7V2ZQX9l7rgbPg7vjS9mt6U5HXODVFVI2bOduCzwOMv/lw==" + } + } + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -4095,8 +4130,7 @@ "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "collection-visit": { "version": "1.0.0", @@ -4215,6 +4249,11 @@ "xdg-basedir": "^3.0.0" } }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" + }, "content-disposition": { "version": "0.5.2", "resolved": "http://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", @@ -4469,8 +4508,7 @@ "deep-extend": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==" }, "deep-is": { "version": "0.1.3", @@ -4582,6 +4620,11 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, "denque": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", @@ -4597,6 +4640,11 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" + }, "detect-newline": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", @@ -5566,6 +5614,14 @@ "universalify": "^0.1.0" } }, + "fs-minipass": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.6.tgz", + "integrity": "sha512-crhvyXcMejjv3Z5d2Fa9sf5xLYVCF5O1c71QxbVnbLsmYMBEvDAftewesN/HhY03YRoA7zOMxjNGrF5svGaaeQ==", + "requires": { + "minipass": "^2.2.1" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -6161,6 +6217,21 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, "generate-function": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-1.1.0.tgz", @@ -6435,6 +6506,11 @@ "has-symbol-support-x": "^1.4.1" } }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" + }, "has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", @@ -7000,6 +7076,14 @@ "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=", "dev": true }, + "ignore-walk": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.1.tgz", + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", + "requires": { + "minimatch": "^3.0.4" + } + }, "immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", @@ -7060,8 +7144,7 @@ "ini": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", - "dev": true + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, "inquirer": { "version": "3.3.0", @@ -7304,7 +7387,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, "requires": { "number-is-nan": "^1.0.0" } @@ -9126,6 +9208,30 @@ "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" }, + "minipass": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.5.tgz", + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + }, + "dependencies": { + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" + } + } + }, + "minizlib": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.1.tgz", + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", + "requires": { + "minipass": "^2.2.1" + } + }, "mixin-deep": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", @@ -9465,7 +9571,6 @@ "version": "2.2.4", "resolved": "https://registry.npmjs.org/needle/-/needle-2.2.4.tgz", "integrity": "sha512-HyoqEb4wr/rsoaIDfTH2aVL9nWtQqba2/HvMv+++m8u0dz808MaagKILxtfeSN7QU7nvbQ79zk3vYOJp9zsNEA==", - "dev": true, "requires": { "debug": "^2.1.2", "iconv-lite": "^0.4.4", @@ -9577,6 +9682,34 @@ "which": "^1.3.0" } }, + "node-pre-gyp": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.12.0.tgz", + "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.1", + "needle": "^2.2.1", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4" + }, + "dependencies": { + "nopt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + } + } + }, "nodemon": { "version": "1.18.10", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.18.10.tgz", @@ -9685,6 +9818,20 @@ } } }, + "npm-bundled": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", + "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==" + }, + "npm-packlist": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.4.tgz", + "integrity": "sha512-zTLo8UcVYtDU3gdeaFu2Xu0n0EvelfHDGuqtNIn5RO7yQj4H1TqNdBc/yZjxnWA0PVB8D3Woyp0i5B43JwQ6Vw==", + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", @@ -9693,6 +9840,17 @@ "path-key": "^2.0.0" } }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, "nth-check": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", @@ -9704,8 +9862,7 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "nwsapi": { "version": "2.1.4", @@ -10425,7 +10582,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "dev": true, "requires": { "deep-extend": "^0.6.0", "ini": "~1.3.0", @@ -10991,8 +11147,7 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "set-immediate-shim": { "version": "1.0.1", @@ -12251,7 +12406,6 @@ "version": "1.0.2", "resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -12282,8 +12436,7 @@ "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" }, "superagent": { "version": "3.8.3", @@ -12365,6 +12518,27 @@ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", "dev": true }, + "tar": { + "version": "4.4.10", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.10.tgz", + "integrity": "sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA==", + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.5", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + }, + "dependencies": { + "yallist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" + } + } + }, "tar-fs": { "version": "1.16.3", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz", @@ -13232,6 +13406,14 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "^1.0.2 || 2" + } + }, "widest-line": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-2.0.1.tgz", diff --git a/package.json b/package.json index a2a20d42..61854e15 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "dependencies": { "@sentry/node": "^5.5.0", "@types/ansi-colors": "^3.2.0", + "@types/bcrypt": "^3.0.0", "@types/bcryptjs": "^2.4.2", "@types/body-parser": "^1.17.0", "@types/cheerio": "^0.22.12", @@ -39,6 +40,7 @@ "@types/socket.io": "^2.1.2", "@types/ws": "^6.0.1", "ansi-colors": "^3.2.4", + "bcrypt": "^3.0.6", "bcryptjs": "^2.4.3", "body-parser": "^1.18.3", "cheerio": "^1.0.0-rc.3", diff --git a/src/app/api/favorites/favorites.model.ts b/src/app/api/favorites/favorites.model.ts new file mode 100644 index 00000000..cca2829b --- /dev/null +++ b/src/app/api/favorites/favorites.model.ts @@ -0,0 +1,29 @@ +import { BaseModel, Entity, Field } from '@lib/mongoose'; +import { Types, models, modelNames } from 'mongoose'; +import { Constants } from '@core/helpers'; + +@Entity(Constants.Schemas.favorites) +export class FavoritesSchema { + @Field({ lowercase: false }) public user_id: Types.ObjectId; + @Field({ lowercase: false }) item_id: Types.ObjectId; + @Field({ + enum: [Constants.Schemas.meals, Constants.Schemas.menus] + }) public type: string; + + // @Virtual('assigned', { + // ref: doc => doc.type, + // localField: 'item_id', + // foreignField: '_id', + // justOne: false + // }) + // t(){} +} +export const FavoritesModel = BaseModel(FavoritesSchema); +FavoritesModel.schema.virtual('items', { + ref: doc => doc.type, + localField: 'item_id', + foreignField: '_id', + justOne: true +}); + + diff --git a/src/app/api/favorites/favorites.repo.ts b/src/app/api/favorites/favorites.repo.ts new file mode 100644 index 00000000..837bc2c0 --- /dev/null +++ b/src/app/api/favorites/favorites.repo.ts @@ -0,0 +1,31 @@ +import { Body } from '@lib/mongoose'; +import { FavoritesModel, FavoritesSchema } from './favorites.model'; +import { models } from 'mongoose'; + +type FavoritesModelBody = Body; + +export class FavoritesRepo extends FavoritesModel { + + public static async createEntity(doc: FavoritesModelBody) { + const entity = new FavoritesRepo(doc); + const type = doc.type; + if (!models[type]) { + return { passed: false, msg: 'is not one of the supported type' } + } + const isThere = await models[type].findById(doc.item_id).lean(); + if (!isThere) { + return { passed: false, msg: 'type is not associated with the item_id' } + } + console.warn('passed'); + return { passed: true, entity: entity.save() }; + } + + public static deleteEntity(id: string) { + return this.findOneAndDelete({ _id: id }); + } + + public static fetchEntities(obj?: Partial, ...args) { + return this.find(obj, ...args); + } + +} diff --git a/src/app/api/favorites/favorites.routes.ts b/src/app/api/favorites/favorites.routes.ts new file mode 100644 index 00000000..95507c5f --- /dev/null +++ b/src/app/api/favorites/favorites.routes.ts @@ -0,0 +1,51 @@ +import { Auth } from '@api/portal'; +import { ErrorResponse, NetworkStatus, SuccessResponse, tokenService } from '@core/helpers'; +import { Logger } from '@core/utils'; +import { Delete, Get, Post, Router } from '@lib/methods'; +import { translate } from '@lib/translation'; +import { Request, Response } from 'express'; +import { FavoritesRepo } from './favorites.repo'; +const log = new Logger('FavoritesRouter'); +import { Constants } from '@core/helpers'; +import { Types } from 'mongoose'; + +@Router(Constants.Endpoints.favorites) +export class FavoritesRouter { + private repo = FavoritesRepo; + + @Post('', Auth.isAuthenticated) + public async create(req: Request, res: Response) { + const { item_id, type } = req.body; + // TODO: make an interface for user token + const decodedToken = await tokenService.decodeToken(req.headers.authorization); + const result = await this.repo.createEntity({ type, user_id: decodedToken.id, item_id }); + let response; + if (result.passed) { + response = new SuccessResponse(result.entity, translate('success'), NetworkStatus.CREATED); + } else { + response = new ErrorResponse(result.msg, NetworkStatus.BAD_REQUEST); + } + res.status(response.code).json(response); + } + + @Delete(':id', Auth.isAuthenticated) + public async delete(req: Request, res: Response) { + const { id } = req.params; + const entity = await this.repo.deleteEntity(id); + if (!entity) { + throw new ErrorResponse(translate('entity_not_found'), NetworkStatus.NOT_ACCEPTABLE); + } + const response = new SuccessResponse(null, translate('success'), NetworkStatus.OK); + res.status(response.code).json(response); + } + + @Get(':type') + public async fetchFavMeals(req: Request, res: Response) { + const decodedToken = await tokenService.decodeToken(req.headers.authorization); + const entites = await this.repo.fetchEntities({ user_id: decodedToken.id, type: req.params.type }).populate('items'); + const response = new SuccessResponse(entites, translate('success'), NetworkStatus.OK); + response['count'] = entites.length; + res.status(response.code).json(response); + } + +} diff --git a/src/app/api/favorites/index.ts b/src/app/api/favorites/index.ts new file mode 100644 index 00000000..c2388e64 --- /dev/null +++ b/src/app/api/favorites/index.ts @@ -0,0 +1,3 @@ +export * from './favorites.model'; +export * from './favorites.repo'; +export * from './favorites.routes'; diff --git a/src/app/api/meals/meals.model.ts b/src/app/api/meals/meals.model.ts index 67e11acc..c97dd43e 100644 --- a/src/app/api/meals/meals.model.ts +++ b/src/app/api/meals/meals.model.ts @@ -1,7 +1,8 @@ import { BaseModel, Entity, Field } from '@lib/mongoose'; import { Types } from 'mongoose'; +import { Constants } from '@core/helpers'; -@Entity('meals') +@Entity(Constants.Schemas.meals) export class MealsSchema { @Field() public name: string; @Field() public recipe: string; diff --git a/src/app/api/meals/meals.routes.ts b/src/app/api/meals/meals.routes.ts index e3003338..9b8ccafa 100644 --- a/src/app/api/meals/meals.routes.ts +++ b/src/app/api/meals/meals.routes.ts @@ -1,13 +1,14 @@ import { Auth } from '@api/portal'; import { ErrorResponse, NetworkStatus, SuccessResponse } from '@core/helpers'; import { Logger } from '@core/utils'; +import { Constants } from '@core/helpers'; import { Delete, Get, Post, Put, Router } from '@lib/methods'; import { translate } from '@lib/translation'; import { Request, Response } from 'express'; import { MealsRepo } from './meals.repo'; const log = new Logger('MealsRouter'); -@Router('meals') +@Router(Constants.Endpoints.meals) export class MealsRouter { private repo = MealsRepo; @@ -55,20 +56,19 @@ export class MealsRouter { @Get() public async fetchEntities(req: Request, res: Response) { - const response = await this.emitValue(); + const response = await this.populateAllEntityQuery(); res.status(response.code).json(response); } @Get('menu/:menu_id') public async fetchEntitiesByMealID(req: Request, res: Response) { const { menu_id } = req.params; - const response = await this.emitValue({ menu_id }); + const response = await this.populateAllEntityQuery({ menu_id }); res.status(response.code).json(response); } - public async emitValue(query = {}) { - log.debug(query); + public async populateAllEntityQuery(query = {}) { const entites = await this.repo.fetchEntities(query); const response = new SuccessResponse(entites, translate('success'), NetworkStatus.OK); response['count'] = entites.length; diff --git a/src/app/api/meals/meals.spe.ts b/src/app/api/meals/meals.spe.ts deleted file mode 100644 index 2032d74a..00000000 --- a/src/app/api/meals/meals.spe.ts +++ /dev/null @@ -1,82 +0,0 @@ -// FIXME ignore testing until correctly test { USERS } -// import { sign } from 'jsonwebtoken'; -// import { Types } from 'mongoose'; -// import supertest = require('supertest'); -// import { JestRequest } from '../../../../test'; -// let id = null; -// let token = null; -// let client: supertest.SuperTest = null; -// const ENDPOINT = '/api/meals'; -// beforeAll(async () => { -// client = await JestRequest(); -// const req = client.post(`${ENDPOINT}`); -// req.send({ -// recipe: 'Test recipe', -// name: 'Test male', -// image: 'Test image', -// price: 10, -// menu_id: 10 -// }); -// const res = await req; -// console.log(res.body); -// id = res.body.data._id; -// token = sign({ id }, process.env.JWT_SECRET_KEY); -// }); - -// afterAll(async () => { -// const req = client.delete(`${ENDPOINT}/${id}`); -// await req.set('Authorization', token); -// }); - -// // NOTE test the fail, don't test the success -// describe('GET ALL', () => { -// test('Reject request without token', async () => { -// const res = await client.get(`${ENDPOINT}`); -// expect(res.status).toBe(401); -// }); - -// test('Should return list', async () => { -// const req = client.get(`${ENDPOINT}`); -// const res = await req.set('Authorization', token); -// expect(res.status).toBe(200); -// expect(res.body.data).toBeInstanceOf(Array); -// }); -// }); - -// describe('GET BY ${id}/', () => { -// test('Reject request without token', async () => { -// const res = await client.get(`${ENDPOINT}/${id}`); -// expect(res.status).toBe(401); -// }); - -// test('should fail if requested with id not of type ObjectId', async () => { -// const req = client.get(`${ENDPOINT}/${undefined}`); -// const res = await req.set('Authorization', token); -// expect(res.status).toBe(400); -// }); - -// test('should fail if requested to non exist entity', async () => { -// const req = client.get(`${ENDPOINT}/${new Types.ObjectId()}`); -// const res = await req.set('Authorization', token); -// expect(res.status).toBe(406); -// }); - -// test('resposne body should equal to', async () => { -// const req = client.get(`${ENDPOINT}/${id}`); -// const res = await req.set('Authorization', token); -// const { data } = res.body; -// expect(data).toHaveProperty('image'); -// expect(data).toHaveProperty('price'); -// expect(data).toHaveProperty('recipe'); -// expect(data).toHaveProperty('name'); -// expect(data).toHaveProperty('createdAt'); -// expect(data).toHaveProperty('updatedAt'); -// expect(data).toHaveProperty('_id'); -// }); -// }); - -// // tslint:disable-next-line: max-line-length -// // REVIEW in create and update you should check and verify if the data was update or created successfully other that the failur test -// // and in delete you must check that the entity no longer in database -// // in update and create you must test the validation by insert Wrong data -// // in login check login test cases diff --git a/src/app/api/menus/menus.model.ts b/src/app/api/menus/menus.model.ts index 5b774235..017e2677 100644 --- a/src/app/api/menus/menus.model.ts +++ b/src/app/api/menus/menus.model.ts @@ -1,6 +1,7 @@ import { BaseModel, Entity, Field } from '@lib/mongoose'; +import { Constants } from '@core/helpers'; -@Entity('menus') +@Entity(Constants.Schemas.menus) export class MenusSchema { @Field() public name: string; @Field() public description: string; diff --git a/src/app/api/menus/menus.routes.ts b/src/app/api/menus/menus.routes.ts index 8777ed4f..23731cc1 100644 --- a/src/app/api/menus/menus.routes.ts +++ b/src/app/api/menus/menus.routes.ts @@ -6,8 +6,9 @@ import { translate } from '@lib/translation'; import { Request, Response } from 'express'; import { MenusRepo } from './menus.repo'; const log = new Logger('MenusRouter'); +import { Constants } from '@core/helpers'; -@Router('menus') +@Router(Constants.Endpoints.menus) export class MenusRouter { private repo = MenusRepo; diff --git a/src/app/api/portal/auth.ts b/src/app/api/portal/auth.ts index 21aa75a9..b9f3bef6 100644 --- a/src/app/api/portal/auth.ts +++ b/src/app/api/portal/auth.ts @@ -1,7 +1,7 @@ import { ErrorResponse, NetworkStatus } from '@core/helpers'; import { Logger } from '@core/utils'; +import { tokenService } from '@core/helpers'; import { NextFunction, Request, Response } from 'express'; -import jwt = require('jsonwebtoken'); const log = new Logger('Auth Module'); @@ -11,7 +11,7 @@ export class Auth { const unauth = new ErrorResponse('Not authorized', NetworkStatus.UNAUTHORIZED); try { const token = req.headers.authorization; - const decodedToken = await verify(token); + const decodedToken = await tokenService.decodeToken(token); log.info('Start checking JWT'); if (!decodedToken) { throw unauth; @@ -22,21 +22,4 @@ export class Auth { next(); } - /** - * - * @param data token payload - * @returns the encrypted token - */ - public static generateToken(data) { - return jwt.sign(data, process.env.JWT_SECRET_KEY); - } -} - -function verify(token: string) { - return new Promise((resolve, reject) => { - jwt.verify(token, process.env.JWT_SECRET_KEY, function(err, decodedToken) { - if (err) { reject(err); } - resolve(decodedToken); - }); - }); } diff --git a/src/app/api/portal/portal.routes.ts b/src/app/api/portal/portal.routes.ts index ec6b54bd..714876a8 100644 --- a/src/app/api/portal/portal.routes.ts +++ b/src/app/api/portal/portal.routes.ts @@ -1,4 +1,4 @@ -import { ErrorResponse, NetworkStatus, SuccessResponse } from '@core/helpers'; +import { ErrorResponse, NetworkStatus, SuccessResponse, tokenService } from '@core/helpers'; import { Post, Router } from '@lib/methods'; import { translate } from '@lib/translation'; import { Request, Response } from 'express'; @@ -22,7 +22,7 @@ export class PortalRoutes { if (isPasswordEqual) { const response = new SuccessResponse(entity, translate('success'), NetworkStatus.OK); log.debug('Start generateToken'); - response['token'] = Auth.generateToken({ id: entity.id }); + response['token'] = tokenService.generateToken({ id: entity.id }); return res.status(response.code).json(response); } } @@ -40,7 +40,7 @@ export class PortalRoutes { if (isPasswordEqual) { const response = new SuccessResponse(entity, translate('success'), NetworkStatus.OK); log.debug('Start generateToken'); - response['token'] = Auth.generateToken({ id: entity.id }); + response['token'] = tokenService.generateToken({ id: entity.id }); return res.status(response.code).json(response); } } diff --git a/src/app/api/users/users.model.ts b/src/app/api/users/users.model.ts index b3215558..9d8135e7 100644 --- a/src/app/api/users/users.model.ts +++ b/src/app/api/users/users.model.ts @@ -5,7 +5,7 @@ import { parsePhoneNumberFromString } from 'libphonenumber-js'; @Entity('users') export class UsersSchema { - @Field() public password: string; + @Field({ lowercase: false }) public password: string; @Field({ match: [ValidationPatterns.NoSpecialChar, 'Value contain special char'], unique: true, diff --git a/src/app/app.ts b/src/app/app.ts index 8d500c8d..05c2b2b1 100644 --- a/src/app/app.ts +++ b/src/app/app.ts @@ -2,7 +2,6 @@ import en from '@assets/languages/en.json'; import { ErrorHandling, stage, StageLevel } from '@core/helpers'; import { Logger } from '@core/utils'; import { translation } from '@lib/translation'; -// import Sentry = require('@sentry/node'); import compression = require('compression'); import express = require('express'); import helmet = require('helmet'); @@ -10,8 +9,10 @@ import morgan = require('morgan'); import { Wrapper } from './wrapper'; import path from 'path'; +// import Sentry = require('@sentry/node'); // import monitor = require('express-status-monitor'); // https://github.com/RafalWilinski/express-status-monitor + const log = new Logger('Application instance'); // Stage.tests(StageLevel.DEV, () => { diff --git a/src/app/core/helpers/constants.ts b/src/app/core/helpers/constants.ts new file mode 100644 index 00000000..c04e88af --- /dev/null +++ b/src/app/core/helpers/constants.ts @@ -0,0 +1,13 @@ +export namespace Constants { + export class Endpoints { + static readonly meals = 'meals'; + static readonly favorites = 'favorites'; + static readonly menus = 'menus'; + + } + export class Schemas { + static readonly favorites = 'favorites'; + static readonly meals = 'meals'; + static readonly menus = 'menus'; + } +} \ No newline at end of file diff --git a/src/app/core/helpers/hash.ts b/src/app/core/helpers/hash.ts index 3fd7e2fe..72413006 100644 --- a/src/app/core/helpers/hash.ts +++ b/src/app/core/helpers/hash.ts @@ -1,5 +1,5 @@ import { Logger } from '@core/utils'; -import bcrypt = require('bcryptjs'); +import bcrypt = require('bcrypt'); const log = new Logger('Hash service'); export class HashService { @@ -13,12 +13,12 @@ export class HashService { } public static hashPassword(passowrd: string) { - log.info('Start hashing password'); + log.info('Start hashing password', typeof passowrd); return this.hashText(passowrd); } public static comparePassword(candidatePassword: string, actualPassword: string) { - log.debug(bcrypt.compareSync(candidatePassword, actualPassword)); + log.debug(typeof candidatePassword, typeof actualPassword); return bcrypt.compare(candidatePassword, actualPassword); } diff --git a/src/app/core/helpers/index.ts b/src/app/core/helpers/index.ts index 7facb252..9fa4ca3b 100644 --- a/src/app/core/helpers/index.ts +++ b/src/app/core/helpers/index.ts @@ -4,3 +4,5 @@ export * from './errors'; export * from './stages'; export * from './errors'; export * from './network-status'; +export * from './token'; +export * from './constants'; \ No newline at end of file diff --git a/src/app/core/helpers/token.ts b/src/app/core/helpers/token.ts new file mode 100644 index 00000000..ce46599b --- /dev/null +++ b/src/app/core/helpers/token.ts @@ -0,0 +1,30 @@ +import jwt = require('jsonwebtoken'); + +class TokenService { + + + /** + * + * @param token + * @returns decoation of the token + */ + public decodeToken(token: string) { + return new Promise((resolve, reject) => { + jwt.verify(token, process.env.JWT_SECRET_KEY, function (err, decodedToken) { + if (err) { reject(err); } + resolve(decodedToken as unknown as T); + }); + }); + } + + /** + * + * @param data token payload + * @returns the encrypted token + */ + public generateToken(data) { + return jwt.sign(data, process.env.JWT_SECRET_KEY); + } +} + +export const tokenService = new TokenService(); \ No newline at end of file diff --git a/src/app/server.ts b/src/app/server.ts index 8463eb79..3b457bd4 100644 --- a/src/app/server.ts +++ b/src/app/server.ts @@ -5,10 +5,9 @@ import { envirnoment } from '@environment/env'; import http = require('http'); import { URL } from 'url'; import { Application } from './app'; -const log = new Logger('Server init'); -import { OLHC, loadOHLCcsv } from './playground/olhc'; +import { OLHC } from './playground/olhc'; import ohlcJson from '@assets/data/olhc.json'; -import socketio = require('socket.io'); +const log = new Logger('Server init'); export class NodeServer extends Application { private port = +envirnoment.get('PORT') || 8080; private host = envirnoment.get('HOST') || '127.0.0.1'; diff --git a/src/app/wrapper.ts b/src/app/wrapper.ts index 992e343a..e602246e 100644 --- a/src/app/wrapper.ts +++ b/src/app/wrapper.ts @@ -3,6 +3,7 @@ import { MealsRouter } from '@api/meals'; import { MenusRouter } from '@api/menus'; import { PortalRoutes } from '@api/portal'; import { UsersRouter } from '@api/users'; +import { FavoritesRouter } from '@api/favorites'; import { IExpressInternal, IExpressRouter } from '@lib/methods'; import 'reflect-metadata'; export class Wrapper { @@ -60,7 +61,7 @@ Wrapper.registerRouter(UsersRouter); Wrapper.registerRouter(AdminRouter); Wrapper.registerRouter(MealsRouter); Wrapper.registerRouter(MenusRouter); -// Wrapper.registerRouter(ProductsRouter); +Wrapper.registerRouter(FavoritesRouter); // Wrapper.registerRouter(ArtworksRouter); // Wrapper.registerRouter(CountriesRoutes); // Wrapper.registerRouter(AuthorsRoutes); diff --git a/src/lib/mongoose/entity.ts b/src/lib/mongoose/entity.ts index ca42d75e..d7ed267e 100644 --- a/src/lib/mongoose/entity.ts +++ b/src/lib/mongoose/entity.ts @@ -8,3 +8,11 @@ export function Entity(name: string, options: SchemaOptions = {}) { constructor['wrapper'] = { schema, name }; }; } + +export function Virtual(name: string, options: SchemaOptions = {}) { + return function (constructor: new (...args: any) => any) { + const schema = new BaseSchema(constructor.prototype.fields, options); + schema.loadClass(constructor); + constructor['wrapper'] = { schema, name }; + }; +} diff --git a/src/test.json b/src/test.json deleted file mode 100644 index cafe84d8..00000000 --- a/src/test.json +++ /dev/null @@ -1 +0,0 @@ -{"source":"{ type: Sequelize.DOUBLE }","order_id":"{ type: Sequelize.DOUBLE }","order_ref":"{ type: Sequelize.DOUBLE }","request_time":"{ type: Sequelize.DOUBLE }","platform":"{ type: Sequelize.DOUBLE }","client":"{ type: Sequelize.DOUBLE }","underlying_client":"{ type: Sequelize.DOUBLE }","account":"{ type: Sequelize.DOUBLE }","side":"{ type: Sequelize.DOUBLE }","symbol":"{ type: Sequelize.DOUBLE }","instrument_type":"{ type: Sequelize.DOUBLE }","type":"{ type: Sequelize.DOUBLE }","size":"{ type: Sequelize.DOUBLE }","size_usd":"{ type: Sequelize.DOUBLE }","price":"{ type: Sequelize.DOUBLE }","markup":"{ type: Sequelize.DOUBLE }","status":"{ type: Sequelize.DOUBLE }","fill_size":"{ type: Sequelize.DOUBLE }","fill_size_usd":"{ type: Sequelize.DOUBLE }","fill_price":"{ type: Sequelize.DOUBLE }","message":"{ type: Sequelize.DOUBLE }","exec_time":"{ type: Sequelize.DOUBLE }","is_mt4_trade":"{ type: Sequelize.DOUBLE }","mt4_ticket":"{ type: Sequelize.DOUBLE }","mt4_side":"{ type: Sequelize.DOUBLE }","mt4_comment":"{ type: Sequelize.DOUBLE }"} \ No newline at end of file