diff --git a/package-lock.json b/package-lock.json index 3abe88c997..05495514f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -119,17 +119,17 @@ } }, "@babel/runtime": { - "version": "7.4.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.4.5.tgz", - "integrity": "sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ==", + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.5.5.tgz", + "integrity": "sha512-28QvEGyQyNkB0/m2B4FU7IEZGK2NUrcMtT6BZEFALTguLk+AUT6ofsHtPk5QyjAdUkpMJ+/Em+quwz4HOt30AQ==", "requires": { "regenerator-runtime": "^0.13.2" }, "dependencies": { "regenerator-runtime": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz", - "integrity": "sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==" + "version": "0.13.3", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.3.tgz", + "integrity": "sha512-naKIZz2GQ8JWh///G7L3X6LaQUAMp2lvb1rvwwsURe/VXwD6VMfr+/1NuNw3ag8v2kY1aQ/go5SNn79O9JU7yw==" } } }, @@ -209,6 +209,34 @@ "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.4.0.tgz", "integrity": "sha512-yOvY4vhouiTXMNHMlxdgjjC6rdmspQTFCMaRDcxzOK+LP9YfoIccXpOK9boyUgvA1U/pgzHlVZDHrWs6r3sRcA==" }, + "@types/chai": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.1.7.tgz", + "integrity": "sha512-2Y8uPt0/jwjhQ6EiluT0XCri1Dbplr0ZxfFXUz+ye13gaqE8u5gL5ppao1JrUYr9cIip5S6MvQzBS7Kke7U9VA==", + "dev": true + }, + "@types/cookiejar": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.1.tgz", + "integrity": "sha512-aRnpPa7ysx3aNW60hTiCtLHlQaIFsXFCgQlpakNgDNVFzbtusSY8PwjAQgRWfSk0ekNoBjO51eQRB6upA9uuyw==", + "dev": true + }, + "@types/node": { + "version": "12.0.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.3.tgz", + "integrity": "sha512-zkOxCS/fA+3SsdA+9Yun0iANxzhQRiNwTvJSr6N95JhuJ/x27z9G2URx1Jpt3zYFfCGUXZGL5UDxt5eyLE7wgw==", + "dev": true + }, + "@types/superagent": { + "version": "3.8.7", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-3.8.7.tgz", + "integrity": "sha512-9KhCkyXv268A2nZ1Wvu7rQWM+BmdYUVkycFeNnYrUL5Zwu7o8wPQ3wBfW59dDP+wuoxw0ww8YKgTNv8j/cgscA==", + "dev": true, + "requires": { + "@types/cookiejar": "*", + "@types/node": "*" + } + }, "@webassemblyjs/ast": { "version": "1.5.13", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.5.13.tgz", @@ -592,7 +620,8 @@ "ansi-html": { "version": "0.0.7", "resolved": "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz", - "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=" + "integrity": "sha1-gTWEAhliqenm/QOflA0S9WynhZ4=", + "dev": true }, "ansi-regex": { "version": "3.0.0", @@ -924,6 +953,537 @@ "is-glob": "^2.0.0", "path-is-absolute": "^1.0.0", "readdirp": "^2.0.0" + }, + "dependencies": { + "fsevents": { + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", + "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", + "dev": true, + "optional": true, + "requires": { + "nan": "^2.12.1", + "node-pre-gyp": "^0.12.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "debug": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "^2.1.1" + } + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "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" + } + }, + "glob": { + "version": "7.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "iconv-lite": { + "version": "0.4.24", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "minipass": { + "version": "2.3.5", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "needle": { + "version": "2.3.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^4.1.0", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, + "node-pre-gyp": { + "version": "0.12.0", + "bundled": true, + "dev": true, + "optional": true, + "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" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "rimraf": { + "version": "2.6.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "dev": true, + "optional": true + }, + "semver": { + "version": "5.7.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "4.4.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.2" + } + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true, + "dev": true + } + } + } } }, "convert-source-map": { @@ -2512,6 +3072,12 @@ } } }, + "call-me-maybe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", + "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", + "dev": true + }, "caller-path": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", @@ -2582,6 +3148,21 @@ "check-error": "^1.0.2" } }, + "chai-http": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/chai-http/-/chai-http-4.3.0.tgz", + "integrity": "sha512-zFTxlN7HLMv+7+SPXZdkd5wUlK+KxH6Q7bIEMiEx0FK3zuuMqL7cwICAQ0V1+yYRozBburYuxN1qZstgHpFZQg==", + "dev": true, + "requires": { + "@types/chai": "4", + "@types/superagent": "^3.8.3", + "cookiejar": "^2.1.1", + "is-ip": "^2.0.0", + "methods": "^1.1.2", + "qs": "^6.5.1", + "superagent": "^3.7.0" + } + }, "chalk": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", @@ -3332,9 +3913,9 @@ } }, "date-fns": { - "version": "2.0.0-alpha.34", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.0.0-alpha.34.tgz", - "integrity": "sha512-yjSYUHASHvzOZl++cEms+Tw7oQOFA+7Z6/lL7L3lRO9j6pMfT48N6oEyvCGo/MVlH08XWmydgf8X9Y1eedf9sQ==" + "version": "2.0.0-beta.3", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.0.0-beta.3.tgz", + "integrity": "sha512-z5O262BvHPhwUvA1weXH+AZodygnZUcORERw8hjwBUrRPGrAo2e/rjXfC8Ykf1OGJZGDuLnK/WXbEZBIc0exGQ==" }, "date-now": { "version": "0.1.4", @@ -3711,9 +4292,9 @@ "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "ejs": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.2.tgz", - "integrity": "sha512-PcW2a0tyTuPHz3tWyYqtK6r1fZ3gp+3Sop8Ph+ZYN81Ob5rwmbHEzaqs10N3BEsaGTkh/ooniXK+WwszGlc2+Q==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.1.tgz", + "integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==", "dev": true }, "elasticsearch": { @@ -4803,6 +5384,12 @@ } } }, + "faker": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/faker/-/faker-4.1.0.tgz", + "integrity": "sha1-HkW7vsxndLPBlfrSg1EJxtdIzD8=", + "dev": true + }, "fast-deep-equal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", @@ -5088,6 +5675,12 @@ "mime-types": "^2.1.12" } }, + "format-util": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/format-util/-/format-util-1.0.3.tgz", + "integrity": "sha1-Ay3KShFiYqEsQ/TD7IVmQWxbLZU=", + "dev": true + }, "formidable": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.1.tgz", @@ -5150,36 +5743,32 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", - "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", + "version": "1.2.9", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.9.tgz", + "integrity": "sha512-oeyj2H3EjjonWcFjD5NvZNE9Rqe4UW+nQBU2HNeKw0koVLEFIhtyETyAakeAM3de7Z/SW5kcA+fZUait9EApnw==", "optional": true, "requires": { - "nan": "^2.9.2", - "node-pre-gyp": "^0.10.0" + "nan": "^2.12.1", + "node-pre-gyp": "^0.12.0" }, "dependencies": { "abbrev": { "version": "1.1.1", - "resolved": false, - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "bundled": true, "optional": true }, "ansi-regex": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "bundled": true }, "aproba": { "version": "1.2.0", - "resolved": false, - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "bundled": true, "optional": true }, "are-we-there-yet": { - "version": "1.1.4", - "resolved": false, - "integrity": "sha1-u13KOCu5TwXhUZQ3PRb9O6HKEQ0=", + "version": "1.1.5", + "bundled": true, "optional": true, "requires": { "delegates": "^1.0.0", @@ -5188,78 +5777,64 @@ }, "balanced-match": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "bundled": true }, "brace-expansion": { "version": "1.1.11", - "resolved": false, - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "bundled": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "chownr": { - "version": "1.0.1", - "resolved": false, - "integrity": "sha1-4qdQQqlVGQi+vSW4Uj1fl2nXkYE=", + "version": "1.1.1", + "bundled": true, "optional": true }, "code-point-at": { "version": "1.1.0", - "resolved": false, - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "optional": true + "bundled": true }, "concat-map": { "version": "0.0.1", - "resolved": false, - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "bundled": true }, "console-control-strings": { "version": "1.1.0", - "resolved": false, - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "optional": true + "bundled": true }, "core-util-is": { "version": "1.0.2", - "resolved": false, - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "bundled": true, "optional": true }, "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "version": "4.1.1", + "bundled": true, "optional": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, "deep-extend": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.5.1.tgz", - "integrity": "sha512-N8vBdOa+DF7zkRrDCsaOXoCs/E2fJfx9B9MrKnnSiHNh4ws7eSys6YQE4KvT1cecKmOASYQBhbKjeuDD9lT81w==", + "version": "0.6.0", + "bundled": true, "optional": true }, "delegates": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "bundled": true, "optional": true }, "detect-libc": { "version": "1.0.3", - "resolved": false, - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "bundled": true, "optional": true }, "fs-minipass": { "version": "1.2.5", - "resolved": false, - "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", + "bundled": true, "optional": true, "requires": { "minipass": "^2.2.1" @@ -5267,14 +5842,12 @@ }, "fs.realpath": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "bundled": true, "optional": true }, "gauge": { "version": "2.7.4", - "resolved": false, - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "bundled": true, "optional": true, "requires": { "aproba": "^1.0.3", @@ -5288,9 +5861,8 @@ } }, "glob": { - "version": "7.1.2", - "resolved": false, - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "version": "7.1.3", + "bundled": true, "optional": true, "requires": { "fs.realpath": "^1.0.0", @@ -5303,23 +5875,20 @@ }, "has-unicode": { "version": "2.0.1", - "resolved": false, - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "bundled": true, "optional": true }, "iconv-lite": { - "version": "0.4.21", - "resolved": false, - "integrity": "sha512-En5V9za5mBt2oUA03WGD3TwDv0MKAruqsuxstbMUZaj9W9k/m1CV/9py3l0L5kw9Bln8fdHQmzHSYtvpvTLpKw==", + "version": "0.4.24", + "bundled": true, "optional": true, "requires": { - "safer-buffer": "^2.1.0" + "safer-buffer": ">= 2.1.2 < 3" } }, "ignore-walk": { "version": "3.0.1", - "resolved": false, - "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", + "bundled": true, "optional": true, "requires": { "minimatch": "^3.0.4" @@ -5327,8 +5896,7 @@ }, "inflight": { "version": "1.0.6", - "resolved": false, - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "bundled": true, "optional": true, "requires": { "once": "^1.3.0", @@ -5337,57 +5905,47 @@ }, "inherits": { "version": "2.0.3", - "resolved": false, - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "optional": true + "bundled": true }, "ini": { "version": "1.3.5", - "resolved": false, - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "bundled": true, "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "optional": true, + "bundled": true, "requires": { "number-is-nan": "^1.0.0" } }, "isarray": { "version": "1.0.0", - "resolved": false, - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "bundled": true, "optional": true }, "minimatch": { "version": "3.0.4", - "resolved": false, - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "bundled": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "bundled": true }, "minipass": { - "version": "2.2.4", - "resolved": false, - "integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==", + "version": "2.3.5", + "bundled": true, "requires": { - "safe-buffer": "^5.1.1", + "safe-buffer": "^5.1.2", "yallist": "^3.0.0" } }, "minizlib": { - "version": "1.1.0", - "resolved": false, - "integrity": "sha512-4T6Ur/GctZ27nHfpt9THOdRZNgyJ9FZchYO1ceg5S8Q3DNLCKYy44nCZzgCJgcvx2UM8czmqak5BCxJMrq37lA==", + "version": "1.2.1", + "bundled": true, "optional": true, "requires": { "minipass": "^2.2.1" @@ -5395,42 +5953,38 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": false, - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "bundled": true, "requires": { "minimist": "0.0.8" } }, "ms": { - "version": "2.0.0", - "resolved": false, - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "version": "2.1.1", + "bundled": true, "optional": true }, "needle": { - "version": "2.2.0", - "resolved": false, - "integrity": "sha512-eFagy6c+TYayorXw/qtAdSvaUpEbBsDwDyxYFgLZ0lTojfH7K+OdBqAF7TAFwDokJaGpubpSGG0wO3iC0XPi8w==", + "version": "2.3.0", + "bundled": true, "optional": true, "requires": { - "debug": "^2.1.2", + "debug": "^4.1.0", "iconv-lite": "^0.4.4", "sax": "^1.2.4" } }, "node-pre-gyp": { - "version": "0.10.0", - "resolved": false, - "integrity": "sha512-G7kEonQLRbcA/mOoFoxvlMrw6Q6dPf92+t/l0DFSMuSlDoWaI9JWIyPwK0jyE1bph//CUEL65/Fz1m2vJbmjQQ==", + "version": "0.12.0", + "bundled": true, "optional": true, "requires": { "detect-libc": "^1.0.2", "mkdirp": "^0.5.1", - "needle": "^2.2.0", + "needle": "^2.2.1", "nopt": "^4.0.1", "npm-packlist": "^1.1.6", "npmlog": "^4.0.2", - "rc": "^1.1.7", + "rc": "^1.2.7", "rimraf": "^2.6.1", "semver": "^5.3.0", "tar": "^4" @@ -5438,8 +5992,7 @@ }, "nopt": { "version": "4.0.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", - "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "bundled": true, "optional": true, "requires": { "abbrev": "1", @@ -5447,15 +6000,13 @@ } }, "npm-bundled": { - "version": "1.0.3", - "resolved": false, - "integrity": "sha512-ByQ3oJ/5ETLyglU2+8dBObvhfWXX8dtPZDMePCahptliFX2iIuhyEszyFk401PZUNQH20vvdW5MLjJxkwU80Ow==", + "version": "1.0.6", + "bundled": true, "optional": true }, "npm-packlist": { - "version": "1.1.10", - "resolved": false, - "integrity": "sha512-AQC0Dyhzn4EiYEfIUjCdMl0JJ61I2ER9ukf/sLxJUcZHfo+VyEfz2rMJgLZSS1v30OxPQe1cN0LZA1xbcaVfWA==", + "version": "1.4.1", + "bundled": true, "optional": true, "requires": { "ignore-walk": "^3.0.1", @@ -5464,8 +6015,7 @@ }, "npmlog": { "version": "4.1.2", - "resolved": false, - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "bundled": true, "optional": true, "requires": { "are-we-there-yet": "~1.1.2", @@ -5476,40 +6026,33 @@ }, "number-is-nan": { "version": "1.0.1", - "resolved": false, - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "optional": true + "bundled": true }, "object-assign": { "version": "4.1.1", - "resolved": false, - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "bundled": true, "optional": true }, "once": { "version": "1.4.0", - "resolved": false, - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "bundled": true, "requires": { "wrappy": "1" } }, "os-homedir": { "version": "1.0.2", - "resolved": false, - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "bundled": true, "optional": true }, "os-tmpdir": { "version": "1.0.2", - "resolved": false, - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "bundled": true, "optional": true }, "osenv": { "version": "0.1.5", - "resolved": false, - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "bundled": true, "optional": true, "requires": { "os-homedir": "^1.0.0", @@ -5518,23 +6061,20 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": false, - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "bundled": true, "optional": true }, "process-nextick-args": { "version": "2.0.0", - "resolved": false, - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "bundled": true, "optional": true }, "rc": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.7.tgz", - "integrity": "sha512-LdLD8xD4zzLsAT5xyushXDNscEjB7+2ulnl8+r1pnESlYtlJtVSoCMBGr30eDRJ3+2Gq89jK9P9e4tCEH1+ywA==", + "version": "1.2.8", + "bundled": true, "optional": true, "requires": { - "deep-extend": "^0.5.1", + "deep-extend": "^0.6.0", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" @@ -5542,16 +6082,14 @@ "dependencies": { "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", + "bundled": true, "optional": true } } }, "readable-stream": { "version": "2.3.6", - "resolved": false, - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "bundled": true, "optional": true, "requires": { "core-util-is": "~1.0.0", @@ -5564,54 +6102,45 @@ } }, "rimraf": { - "version": "2.6.2", - "resolved": false, - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "version": "2.6.3", + "bundled": true, "optional": true, "requires": { - "glob": "^7.0.5" + "glob": "^7.1.3" } }, "safe-buffer": { - "version": "5.1.1", - "resolved": false, - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + "version": "5.1.2", + "bundled": true }, "safer-buffer": { "version": "2.1.2", - "resolved": false, - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "bundled": true, "optional": true }, "sax": { "version": "1.2.4", - "resolved": false, - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "bundled": true, "optional": true }, "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "version": "5.7.0", + "bundled": true, "optional": true }, "set-blocking": { "version": "2.0.0", - "resolved": false, - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "bundled": true, "optional": true }, "signal-exit": { "version": "3.0.2", - "resolved": false, - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "bundled": true, "optional": true }, "string-width": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "optional": true, + "bundled": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5620,8 +6149,7 @@ }, "string_decoder": { "version": "1.1.1", - "resolved": false, - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "bundled": true, "optional": true, "requires": { "safe-buffer": "~5.1.0" @@ -5629,57 +6157,50 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "bundled": true, "requires": { "ansi-regex": "^2.0.0" } }, "strip-json-comments": { "version": "2.0.1", - "resolved": false, - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "bundled": true, "optional": true }, "tar": { - "version": "4.4.1", - "resolved": false, - "integrity": "sha512-O+v1r9yN4tOsvl90p5HAP4AEqbYhx4036AGMm075fH9F8Qwi3oJ+v4u50FkT/KkvywNGtwkk0zRI+8eYm1X/xg==", + "version": "4.4.8", + "bundled": true, "optional": true, "requires": { - "chownr": "^1.0.1", + "chownr": "^1.1.1", "fs-minipass": "^1.2.5", - "minipass": "^2.2.4", - "minizlib": "^1.1.0", + "minipass": "^2.3.4", + "minizlib": "^1.1.1", "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.1", + "safe-buffer": "^5.1.2", "yallist": "^3.0.2" } }, "util-deprecate": { "version": "1.0.2", - "resolved": false, - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "bundled": true, "optional": true }, "wide-align": { - "version": "1.1.2", - "resolved": false, - "integrity": "sha512-ijDLlyQ7s6x1JgCLur53osjm/UXUYD9+0PbYKrBsYisYXzCxN+HC3mYDNy/dWdmf3AwqwU3CXwDCvsNgGK1S0w==", + "version": "1.1.3", + "bundled": true, "optional": true, "requires": { - "string-width": "^1.0.2" + "string-width": "^1.0.2 || 2" } }, "wrappy": { "version": "1.0.2", - "resolved": false, - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "bundled": true }, "yallist": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", - "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=" + "version": "3.0.3", + "bundled": true } } }, @@ -5914,9 +6435,9 @@ "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" }, "growl": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", - "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.3.tgz", + "integrity": "sha512-hKlsbA5Vu3xsh1Cg3J7jSmX/WaW6A5oBeqzM88oNbCRQFz+zUaXm6yxS4RVytp1scBoJzSYl4YAEOQIt6O8V1Q==", "dev": true }, "gud": { @@ -6153,7 +6674,8 @@ "html-entities": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", - "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=" + "integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8=", + "dev": true }, "htmlescape": { "version": "1.1.1", @@ -6433,6 +6955,12 @@ "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", "dev": true }, + "ip-regex": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", + "dev": true + }, "ipaddr.js": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", @@ -6612,6 +7140,15 @@ "is-path-inside": "^1.0.0" } }, + "is-ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-ip/-/is-ip-2.0.0.tgz", + "integrity": "sha1-aO6gfooKCpTC0IDdZ0xzGrKkYas=", + "dev": true, + "requires": { + "ip-regex": "^2.0.0" + } + }, "is-npm": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", @@ -7119,9 +7656,9 @@ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" }, "js-yaml": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", - "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", "dev": true, "requires": { "argparse": "^1.0.7", @@ -7241,6 +7778,18 @@ "dev": true, "optional": true }, + "json-schema-ref-parser": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-5.1.3.tgz", + "integrity": "sha512-CpDFlBwz/6la78hZxyB9FECVKGYjIIl3Ms3KLqFj99W7IIb7D00/RDgc++IGB4BBALl0QRhh5m4q5WNSopvLtQ==", + "dev": true, + "requires": { + "call-me-maybe": "^1.0.1", + "debug": "^3.1.0", + "js-yaml": "^3.12.0", + "ono": "^4.0.6" + } + }, "json-schema-traverse": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", @@ -7284,6 +7833,18 @@ "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=" }, + "jsonschema": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.2.4.tgz", + "integrity": "sha512-lz1nOH69GbsVHeVgEdvyavc/33oymY1AZwtePMiMj4HZPMbP5OIKK3zT9INMWjwua/V4Z4yq7wSlBbSG+g4AEw==", + "dev": true + }, + "jsonschema-draft4": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/jsonschema-draft4/-/jsonschema-draft4-1.0.0.tgz", + "integrity": "sha1-8K8gBQVPDwrefqIRhhS2ncUS2GU=", + "dev": true + }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -7973,6 +8534,12 @@ "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", "dev": true }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", + "dev": true + }, "lodash.isarguments": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", @@ -7983,6 +8550,12 @@ "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=" }, + "lodash.isequal": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=", + "dev": true + }, "lodash.isplainobject": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-3.2.0.tgz", @@ -8420,28 +8993,27 @@ } }, "mocha": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", - "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.0.3.tgz", + "integrity": "sha512-oi0ylWkmIR+IG8OcQkLGPMWhxu/Gfz9EBW7Mztr6FY4JbrPDDi5y9rb2ncNnajZtr2XHfBP+G5pzS6gEzdAcVQ==", "dev": true, "requires": { "browser-stdout": "1.3.1", - "commander": "2.15.1", + "commander": "2.11.0", "debug": "3.1.0", "diff": "3.5.0", "escape-string-regexp": "1.0.5", "glob": "7.1.2", - "growl": "1.10.5", + "growl": "1.10.3", "he": "1.1.1", - "minimatch": "3.0.4", "mkdirp": "0.5.1", - "supports-color": "5.4.0" + "supports-color": "4.4.0" }, "dependencies": { "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", "dev": true }, "glob": { @@ -8458,13 +9030,19 @@ "path-is-absolute": "^1.0.0" } }, + "has-flag": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", + "dev": true + }, "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", + "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "^2.0.0" } } } @@ -8557,9 +9135,9 @@ "dev": true }, "nan": { - "version": "2.11.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.0.tgz", - "integrity": "sha512-F4miItu2rGnV2ySkXOQoA8FKz/SR2Q2sWP0sbTxNxz/tuokeC8WxOhPMcwi0qIyGtVn/rrSeLbvVkznqCdwYnw==", + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", "optional": true }, "nanomatch": { @@ -8908,16 +9486,6 @@ "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", "dev": true }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, - "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - } - }, "lcid": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", @@ -9265,6 +9833,26 @@ "mimic-fn": "^1.0.0" } }, + "ono": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/ono/-/ono-4.0.11.tgz", + "integrity": "sha512-jQ31cORBFE6td25deYeD80wxKBMj+zBmHTrVxnc6CKhx8gho6ipmWM5zj/oeoqioZ99yqBls9Z/9Nss7J26G2g==", + "dev": true, + "requires": { + "format-util": "^1.0.3" + } + }, + "openapi-schema-validation": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/openapi-schema-validation/-/openapi-schema-validation-0.4.2.tgz", + "integrity": "sha512-K8LqLpkUf2S04p2Nphq9L+3bGFh/kJypxIG2NVGKX0ffzT4NQI9HirhiY6Iurfej9lCu7y4Ndm4tv+lm86Ck7w==", + "dev": true, + "requires": { + "jsonschema": "1.2.4", + "jsonschema-draft4": "^1.0.0", + "swagger-schema-official": "2.0.0-bab6bed" + } + }, "opener": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz", @@ -9286,12 +9874,6 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", "dev": true - }, - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "dev": true } } }, @@ -9307,6 +9889,14 @@ "prelude-ls": "~1.1.2", "type-check": "~0.3.2", "wordwrap": "~1.0.0" + }, + "dependencies": { + "wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "dev": true + } } }, "ora": { @@ -9766,9 +10356,9 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "pg": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-7.11.0.tgz", - "integrity": "sha512-YO4V7vCmEMGoF390LJaFaohWNKaA2ayoQOEZmiHVcAUF+YsRThpf/TaKCgSvsSE7cDm37Q/Cy3Gz41xiX/XjTw==", + "version": "7.12.0", + "resolved": "https://registry.npmjs.org/pg/-/pg-7.12.0.tgz", + "integrity": "sha512-q54Ic0oBXfDZMwheP8ALeUX32TUXvF7SNgAlZjyhkDuFCJkQCgcLBz0Be5uOrAj3ljSok/CI9lRbYzEko0z1Zw==", "requires": { "buffer-writer": "2.0.0", "packet-reader": "1.0.0", @@ -9802,9 +10392,9 @@ "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==" }, "pg-pool": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.6.tgz", - "integrity": "sha512-hod2zYQxM8Gt482q+qONGTYcg/qVcV32VHVPtktbBJs0us3Dj7xibISw0BAAXVMCzt8A/jhfJvpZaxUlqtqs0g==" + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-2.0.7.tgz", + "integrity": "sha512-UiJyO5B9zZpu32GSlP0tXy8J2NsJ9EFGFfz5v6PSbdz/1hBLX1rNiiy5+mAm5iJJYwfCv4A0EBcQLGWwjbpzZw==" }, "pg-types": { "version": "2.0.1", @@ -10267,12 +10857,12 @@ } }, "react-datepicker": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-2.7.0.tgz", - "integrity": "sha512-hGe55tPi+ZWqv8GZ4TLKR8J1iCd/DNFM3JA9b5s51nsv+flfWCwrPyPDLxZGnO2aAPZbOazcHpUisCswlYManQ==", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-2.8.0.tgz", + "integrity": "sha512-TL9YemykqK77hq1LRGIWbLFpHvNfSb8yZr3q1zk5XTGdSLsVtZ6mlXby3zuGwi8sL0+h9SEnNaqK8IeOl0R2iQ==", "requires": { "classnames": "^2.2.5", - "date-fns": "^2.0.0-alpha.23", + "date-fns": "^v2.0.0-beta.1", "prop-types": "^15.6.0", "react-onclickoutside": "^6.7.1", "react-popper": "^1.0.2" @@ -11791,6 +12381,62 @@ "has-flag": "^3.0.0" } }, + "swagger-jsdoc": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/swagger-jsdoc/-/swagger-jsdoc-3.2.9.tgz", + "integrity": "sha512-yEGoqnIA5Owb15266x8JyQQM054kyhqkqz/zGxCEsrcx/fq/gf14alEp3qqLpknGOxTr3cqxQr3LrgOxXVm5vg==", + "dev": true, + "requires": { + "commander": "2.17.1", + "doctrine": "2.1.0", + "glob": "7.1.3", + "js-yaml": "3.13.1", + "swagger-parser": "5.0.5" + } + }, + "swagger-methods": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/swagger-methods/-/swagger-methods-1.0.8.tgz", + "integrity": "sha512-G6baCwuHA+C5jf4FNOrosE4XlmGsdjbOjdBK4yuiDDj/ro9uR4Srj3OR84oQMT8F3qKp00tYNv0YN730oTHPZA==", + "dev": true + }, + "swagger-parser": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/swagger-parser/-/swagger-parser-5.0.5.tgz", + "integrity": "sha512-6UiaUT9nH5nEzvxDvwZpTfhCs2VOwxrP9neZ83QpsTA3mMGHdun4x8vSXiqjaGQzLh2LG8ND5TRhmVNG1hRUqA==", + "dev": true, + "requires": { + "call-me-maybe": "^1.0.1", + "debug": "^3.1.0", + "json-schema-ref-parser": "^5.1.3", + "ono": "^4.0.6", + "openapi-schema-validation": "^0.4.2", + "swagger-methods": "^1.0.4", + "swagger-schema-official": "2.0.0-bab6bed", + "z-schema": "^3.23.0" + } + }, + "swagger-schema-official": { + "version": "2.0.0-bab6bed", + "resolved": "https://registry.npmjs.org/swagger-schema-official/-/swagger-schema-official-2.0.0-bab6bed.tgz", + "integrity": "sha1-cAcEaNbSl3ylI3suUZyn0Gouo/0=", + "dev": true + }, + "swagger-ui-dist": { + "version": "3.23.0", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.23.0.tgz", + "integrity": "sha512-DMnt69K3p8BwnKf8f8uOqsts/teZNRS4LlXAqjfa8HS2RKWWOCSiVEHkMzY3zlU4wqu/olPdF0zDEm9Ed5JZ4A==", + "dev": true + }, + "swagger-ui-express": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.0.7.tgz", + "integrity": "sha512-ipXe53qDMjB2GlFcWARof15fMxX0n0wkwUturBpdovfJLaqod3WAqimwQGFXjwpWKA6hnxEPrd31yOzaYkP++A==", + "dev": true, + "requires": { + "swagger-ui-dist": "^3.18.1" + } + }, "symbol-observable": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", @@ -12846,6 +13492,7 @@ "version": "2.23.1", "resolved": "https://registry.npmjs.org/webpack-hot-middleware/-/webpack-hot-middleware-2.23.1.tgz", "integrity": "sha512-oUdCGDHONJrARtqxPSiON4db4dRWUsRiPwD7dYkglrTc3vF7LKa2jncwN0lIVkNwtoCh/yiHVTzz3Hbcux8ikA==", + "dev": true, "requires": { "ansi-html": "0.0.7", "html-entities": "^1.2.0", @@ -12856,12 +13503,14 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, "requires": { "ansi-regex": "^2.0.0" } @@ -12930,9 +13579,9 @@ } }, "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", + "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", "dev": true }, "worker-farm": { @@ -13151,6 +13800,27 @@ "requires": { "camelcase": "^4.1.0" } + }, + "z-schema": { + "version": "3.25.1", + "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-3.25.1.tgz", + "integrity": "sha512-7tDlwhrBG+oYFdXNOjILSurpfQyuVgkRe3hB2q8TEssamDHB7BbLWYkYO98nTn0FibfdFroFKDjndbgufAgS/Q==", + "dev": true, + "requires": { + "commander": "^2.7.1", + "core-js": "^2.5.7", + "lodash.get": "^4.0.0", + "lodash.isequal": "^4.0.0", + "validator": "^10.0.0" + }, + "dependencies": { + "validator": { + "version": "10.11.0", + "resolved": "https://registry.npmjs.org/validator/-/validator-10.11.0.tgz", + "integrity": "sha512-X/p3UZerAIsbBfN/IwahhYaBbY68EN/UQBWHtsbXGT5bfrH/p4NQzUCG1kF/rtKaNpnJ7jAu6NGTdSNtyNIXMw==", + "dev": true + } + } } } } diff --git a/package.json b/package.json index 01cf199fbc..fa0e85bbbc 100644 --- a/package.json +++ b/package.json @@ -7,14 +7,16 @@ "mkdirs": "mkdir -p config static/js/editor static/js/entity static/stylesheets", "clean": "./scripts/clean.sh", "prepublishOnly": "npm run clean && npm run mkdirs && npm run copy-client-scripts", - "postinstall": "npm dedup && npm run prepublishOnly", - "build-server-js": "babel src --out-dir lib", + "postinstall": "npm run prepublishOnly", + "build-server-js": "babel src --out-dir lib --ignore src/api", + "build-api-js": "babel src --out-dir lib --ignore 'src/server','src/client'", "build-client-js": "./scripts/build-client-js.sh", "build-less": "./scripts/build-less.sh", "build": "npm run build-less && npm run build-client-js && npm run build-server-js", "WIP-build-client-webpack": "cross-env NODE_ENV=production SSR=true webpack --progress --config webpack.client.js", "prestart": "npm run build-less & npm run build-client-js & npm run build-server-js", "start": "cross-env SSR=true node ./lib/server/app.js", + "start-api": "npm run build-api-js && node ./lib/api/app.js", "debug": "cross-env DEBUG=bbsite NODE_ENV=development SSR=true babel-node src/server/app.js", "debug-watch-server": "cross-env DEBUG=bbsite NODE_ENV=development SSR=true nodemon src/server/app.js --watch src/server --exec babel-node", "lint": "eslint .", @@ -83,8 +85,7 @@ "superagent": "^3.8.2", "superagent-bluebird-promise": "^4.1.0", "uglify-js": "^3.4.9", - "validator": "^9.1.2", - "webpack-hot-middleware": "^2.22.3" + "validator": "^9.1.2" }, "devDependencies": { "babel-cli": "^6.18.0", @@ -105,6 +106,7 @@ "bootstrap": "^3.4.1", "chai": "^4.0.2", "chai-as-promised": "^7.0.0", + "chai-http": "^4.3.0", "clean-webpack-plugin": "^0.1.19", "cross-env": "^5.1.1", "css-loader": "^1.0.0", @@ -115,6 +117,7 @@ "eslint-plugin-import": "^2.8.0", "eslint-plugin-react": "^7.5.1", "factor-bundle": "^2.3.3", + "faker": "^4.1.0", "file-loader": "^2.0.0", "flow-bin": "^0.69.0", "husky": "^0.14.3", @@ -132,11 +135,14 @@ "rewire": "^3.0.2", "style-loader": "^0.21.0", "supertest": "^3.0.0", + "swagger-jsdoc": "^3.2.9", + "swagger-ui-express": "^4.0.7", "uuid": "^3.3.2", "webpack": "^4.16.4", "webpack-bundle-analyzer": "^3.3.2", "webpack-cli": "^3.1.0", "webpack-dev-middleware": "^3.1.3", + "webpack-hot-middleware": "^2.22.3", "webpack-node-externals": "^1.7.2", "write-assets-webpack-plugin": "^1.0.4" } diff --git a/src/api/app.js b/src/api/app.js new file mode 100644 index 0000000000..ac8226b25a --- /dev/null +++ b/src/api/app.js @@ -0,0 +1,131 @@ +/* + * Copyright (C) 2014-2015 Ben Ockmore + * 2015-2017 Sean Burke + * 2015 Leo Verto + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +/* eslint global-require: 'warn' */ + + +import BookBrainzData from 'bookbrainz-data'; +import Debug from 'debug'; +import Promise from 'bluebird'; +import {get as _get} from 'lodash'; +import {allowOnlyGetMethod} from './helpers/utils'; +import appCleanup from '../common/helpers/appCleanup'; +import bodyParser from 'body-parser'; +import compression from 'compression'; +import config from '../common/helpers/config'; +import express from 'express'; +import logger from 'morgan'; +import path from 'path'; +import redis from 'connect-redis'; +import routes from './routes'; +import session from 'express-session'; +import swaggerRoute from './swagger'; + + +Promise.config({ + longStackTraces: true, + warnings: true +}); + +// Initialize application +const app = express(); +app.locals.orm = BookBrainzData(config.database); + + +app.set('trust proxy', config.site.proxyTrust); + +if (app.get('env') !== 'testing') { + app.use(logger('dev')); +} + +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ + extended: false +})); +app.use(compression()); + + +const RedisStore = redis(session); +app.use(session({ + cookie: { + maxAge: _get(config, 'session.maxAge', 2592000000), + secure: _get(config, 'session.secure', false) + }, + resave: false, + saveUninitialized: false, + secret: config.session.secret, + store: new RedisStore({ + host: _get(config, 'session.redis.host', 'localhost'), + port: _get(config, 'session.redis.port', 6379) + }) +})); + + +// Set up routes +routes(app); + +// use swagger-Ui-express for your app documentation endpoint +app.use('/api-docs', swaggerRoute); + +// Allow only get requests for now throw error for any other type of requests +app.all('/*', allowOnlyGetMethod); + +// Catch 404 and forward to error handler +app.use((req, res, next) => { + res.status(404).send({message: `Incorrect endpoint ${req.path}`}); +}); + + +const debug = Debug('bbapi'); + +const DEFAULT_API_PORT = 9098; +app.set('port', process.env.PORT || DEFAULT_API_PORT); // eslint-disable-line no-process-env,max-len + +const server = app.listen(app.get('port'), () => { + debug(`Express server listening on port ${server.address().port}`); +}); + +/* eslint-disable no-console */ +function cleanupFunction() { + return new Promise((resolve, reject) => { + console.log('Cleaning up before closing'); + server.close((err) => { + if (err) { + console.log('Error while closing server connections'); + reject(err); + } + else { + console.log('Closed all server connections. Bye bye!'); + resolve(); + } + }); + // force-kill after X milliseconds. + if (config.site.forceExitAfterMs) { + setTimeout(() => { + reject(new Error(`Cleanup function timed out after ${config.site.forceExitAfterMs} ms`)); + }, config.site.forceExitAfterMs); + } + }); +} +/* eslint-enable no-console */ + +// Run cleanup function +appCleanup(cleanupFunction); + +export default server; diff --git a/src/api/helpers/entityLoader.js b/src/api/helpers/entityLoader.js new file mode 100644 index 0000000000..abd6a5c9d0 --- /dev/null +++ b/src/api/helpers/entityLoader.js @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2019 Akhilesh Kumar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +// @flow + + +import * as commonUtils from '../../common/helpers/utils'; + +/** + * This is a middleware function to load entity detail according to given relations + * + * @param {string} modelName - Name of entity model + * @param {string[]} relations - List of entity relations for fetching the related detail + * @param {string} errMessage - Error message, if any error will occur + * @returns {object} an object containing the error message if any error will occur. + * If entity is found succesfully in the database this function set the entity data + * at res.locals.entity and return to next function. + * @example + * const errorMessage = 'Edition not found''; + * makeEntityLoader('Edition', ['defaultAlias.language', 'editionStatus'], errMessage); + * + * @description + * First, check the BBID is valid or not. + * If BBID is valid then extract the entity data from database by using BBID and relations of + * that entity. If entity is found succesfully then set that entity data to the res.locals.entity + * otherwise return an object {message: errMessage} as response with status code 404. + * If the BBID is not valid then return a status code 406 and an object {message: 'BBID is not valid uuid'}. + */ + + +export function makeEntityLoader(modelName, relations, errMessage) { + return async (req, res, next) => { + const {orm} = req.app.locals; + if (commonUtils.isValidBBID(req.params.bbid)) { + try { + const entityData = await orm.func.entity.getEntity(orm, modelName, req.params.bbid, relations); + res.locals.entity = entityData; + return next(); + } + catch (err) { + return res.status(404).send({message: errMessage}); + } + } + return res.status(406).send({message: 'BBID is not valid uuid'}); + }; +} diff --git a/src/api/helpers/formatEntityData.js b/src/api/helpers/formatEntityData.js new file mode 100644 index 0000000000..d514bc7314 --- /dev/null +++ b/src/api/helpers/formatEntityData.js @@ -0,0 +1,395 @@ +/* + * Copyright (C) 2019 Akhilesh Kumar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +// @flow + + +import _ from 'lodash'; + +/** + * A function to extract the default alias from ORM entity + * @function + * @param {object} entity - an ORM entity + * @returns {object} an object containing default alias of an ORM entity. + * Each property defaults to null. + * + * @example + * getDefaultAlias(entity); + * /* => { + "aliasLanguage": "English", + "name": "H. Beam Piper", + "sortName": "Piper, H. Beam" + } + */ + +function getDefaultAlias(entity: object) { + return { + aliasLanguage: _.get(entity, 'defaultAlias.language.name', null), + name: _.get(entity, 'defaultAlias.name', null), + primary: _.get(entity, 'defaultAlias.primary', null), + sortName: _.get(entity, 'defaultAlias.sortName', null) + }; +} + +/** + * A function to extract the languages from ORM entity + * @function + * @param {object} entity - an ORM entity + * @returns {string[]} an array containing languages of an ORM entity. + * By default it return [] + * + * @example + * getLanguages(entity); + * /* => ['Hindi', 'English', 'Spanish'] + */ + +function getLanguages(entity: object) { + return _.get(entity, 'languageSet.languages', []).map((language) => language.name); +} + +/** + * A function to extract the basic details of a Work ORM entity + * @function + * @param {object} work - a work ORM entity + * @returns {object} an object containing the basic data of a Work entity. + * Each property defaults to null. If work is null or undefined, returns null. + * + * @example + * const work = await orm.func.entity.getEntity(orm, 'Work', bbid, relations); + * getWorkBasicInfo(work); + * /* => { + "bbid": "ba446064-90a5-447b-abe5-139be547da2e", + "defaultAlias": { + "aliasLanguage": "English", + "name": "Harry Potter", + "primary": true, + "sortName": "Harry Potter" + }, + "disambiguation": null, + "entityType": "Work", + "languages": [ + "English" + ], + "workType": "Epic" + } + */ + +export function getWorkBasicInfo(work: object) { + return _.isNil(work) ? null : + { + bbid: _.get(work, 'bbid', null), + defaultAlias: getDefaultAlias(work), + disambiguation: _.get(work, 'disambiguation.comment', null), + entityType: _.get(work, 'type', null), + languages: getLanguages(work), + workType: _.get(work, 'workType.label', null) + }; +} + +/** + * A function to extract the basic details of an Edition ORM entity + * @function + * @param {object} edition - an edition ORM entity + * @returns {object} an object containing the basic data of an Edition entity. + * Each property defaults to null. If edition is null or undefined, returns null. + * + * @example + * const work = await orm.func.entity.getEntity(orm, 'Edition', bbid, relations); + * getEditionBasicInfo(work); + * /* => { + "bbid": "442ab642-985a-4957-9d61-8a1d9e82de1f", + "defaultAlias": { + "aliasLanguage": "English", + "name": "A Monster Calls", + "primary": true, + "sortName": "Monster Calls, A" + }, + "depth": null, + "disambiguation": null, + "editionFormat": "eBook", + "height": null, + "languages": [ + "English" + ], + "pages": 214, + "releaseEventDates": [ + "2011-01-01" + ], + "status": "Official", + "weight": null, + "width": null + } + */ + +export function getEditionBasicInfo(edition: object) { + return _.isNil(edition) ? null : + { + bbid: _.get(edition, 'bbid', null), + defaultAlias: getDefaultAlias(edition), + depth: _.get(edition, 'depth', null), + disambiguation: _.get(edition, 'disambiguation.comment', null), + editionFormat: _.get(edition, 'editionFormat.label', null), + height: _.get(edition, 'height', null), + languages: getLanguages(edition), + pages: _.get(edition, 'pages', null), + releaseEventDates: _.get(edition, 'releaseEventSet.releaseEvents', []).map((event) => event.date), + status: _.get(edition, 'editionStatus.label', null), + weight: _.get(edition, 'weight', null), + width: _.get(edition, 'width', null) + }; +} + +/** + * A function to extract the basic details of an EditionGroup ORM entity + * @function + * @param {object} editionGroup - an editionGroup ORM entity + * @returns {object} an object containing the basic data of an EditionGroup entity. + * Each property defaults to null. If author is null or undefined, returns null. + * + * @example + * const editionGroup = await orm.func.entity.getEntity(orm, 'EditionGroup', bbid, relations); + * getEditionGroupBasicInfo(editionGroup); + * /* => { + "bbid": "3889b695-70d5-4933-9f08-defad217623e", + "defaultAlias": { + "aliasLanguage": "English", + "name": "A Suitable Boy", + "primary": true, + "sortName": "Suitable Boy, A" + }, + "disambiguation": null, + "type": "Book" + } + */ + +export function getEditionGroupBasicInfo(editionGroup: object) { + return _.isNil(editionGroup) ? null : + { + bbid: _.get(editionGroup, 'bbid', null), + defaultAlias: getDefaultAlias(editionGroup), + disambiguation: _.get(editionGroup, 'disambiguation.comment', null), + type: _.get(editionGroup, 'editionGroupType.label') + }; +} + +/** + * A function to extract the basic details of an Author ORM entity + * @function + * @param {object} author - an Author ORM entity + * @returns {object} an object containing the basic data of an Author entity. + * Each property defaults to null. If author is null or undefined, returns null. + * + * @example + * const author = await orm.func.entity.getEntity(orm, 'Author', bbid, relations); + * getAuthorBasicInfo(author); + * /* => { + "bbid": "86f86a31-7c51-49ed-af71-9523cca30265", + "beginArea": null, + "beginDate": "1904-03-23", + "defaultAlias": { + "aliasLanguage": "English", + "name": "H. Beam Piper", + "primary": true, + "sortName": "Piper, H. Beam" + }, + "disambiguation": null, + "endArea": null, + "endDate": "1964-11-06", + "ended": true, + "gender": "Male", + "type": "Person" + } + */ + +export function getAuthorBasicInfo(author: object) { + return _.isNil(author) ? null : + { + bbid: _.get(author, 'bbid', null), + beginArea: _.get(author, 'beginArea.name', null), + beginDate: _.get(author, 'beginDate', null), + defaultAlias: getDefaultAlias(author), + disambiguation: _.get(author, 'disambiguation.comment', null), + endArea: _.get(author, 'endArea.name', null), + endDate: _.get(author, 'endDate', null), + ended: _.get(author, 'ended', null), + gender: _.get(author, 'gender.name', null), + type: _.get(author, 'authorType.label', null) + }; +} + +/** + * A function to extract the basic details of a Publisher ORM entity + * @function + * @param {object} publisher - a Publisher ORM entity + * @returns {object} an object containing the basic data of a Publisher entity. + * Each property defaults to null. If publisher is null or undefined, returns null. + * + * @example + * const author = await orm.func.entity.getEntity(orm, 'Publisher', bbid, relations); + * getPublisherBasicInfo(publisher); + * /* => { + "area": "India", + "bbid": "e418874e-5684-4fe9-9d2d-1b7e5d43fd59", + "beginDate": "1943", + "defaultAlias": { + "aliasLanguage": "[Multiple languages]", + "name": "Bharati Bhawan", + "primary": true, + "sortName": "Bhawan, Bharati" + }, + "disambiguation": null, + "endDate": null, + "ended": false, + "type": "Publisher" + } + */ + +export function getPublisherBasicInfo(publisher: object) { + return _.isNil(publisher) ? null : + { + area: _.get(publisher, 'area.name', null), + bbid: _.get(publisher, 'bbid', null), + beginDate: _.get(publisher, 'beginDate', null), + defaultAlias: getDefaultAlias(publisher), + disambiguation: _.get(publisher, 'disambiguation.comment', null), + endDate: _.get(publisher, 'endDate', null), + ended: _.get(publisher, 'ended', null), + type: _.get(publisher, 'publisherType.label', null) + }; +} + +/** + * getEntityAliases is a function to extract the list of aliases from ORM entity + * + * @param {object} entity - An ORM Entity + * @returns {object} an object conatining the bbid and list of aliases data of an ORM entity. + * Returns an empty array if no aliases were found + * If entity is null or undefined, returns null. + * @example + * const edition = await orm.func.entity.getEntity(orm, 'Edition', bbid, relations); + * getEntityAliases(edition); + * /* => { + "aliases": [ + { + "aliasLanguage": "English", + "name": "A Monster Calls", + "primary": true, + "sortName": "Monster Calls, A" + } + ], + "bbid": "442ab642-985a-4957-9d61-8a1d9e82de1f" + } + */ + +export function getEntityAliases(entity: object) { + return _.isNil(entity) ? null : + { + aliases: _.get(entity, 'aliasSet.aliases', []).map((alias) => ({ + aliasLanguage: _.get(alias, 'language.name', null), + name: _.get(alias, 'name', null), + primary: _.get(alias, 'primary', null), + sortName: _.get(alias, 'sortName', null) + })), + bbid: _.get(entity, 'bbid', null) + }; +} + +/** + * getEntityIdentifiers is a function to extract the list of indentifiers from an ORM entity + * + * @param {object} entity - An ORM entity + * @returns {object} an object containing the bbid and list of identifier data of an entity. + * Returns an empty array if no identifiers were found. + * If entity object is null or undefined, returns null. + * + * @example + * const edition = await orm.func.entity.getEntity(orm, 'Edition', bbid, relations); + * getEntityIdentifiers(edition); + * /* => { + "bbid": "442ab642-985a-4957-9d61-8a1d9e82de1f", + "identifiers": [ + { + "type": "Amazon ASIN", + "value": "B00A9U9J6C" + } + ] + } + */ + +export function getEntityIdentifiers(entity: object) { + return _.isNil(entity) ? null : + { + bbid: _.get(entity, 'bbid', null), + identifiers: _.get(entity, 'identifierSet.identifiers', []).map((identifier) => ({ + type: _.get(identifier, 'type.label', null), + value: _.get(identifier, 'value', null) + })) + }; +} + +/** + * getEntityRelationships is a function to extract the list of relationships from an ORM entity + * + * @param {object} entity - An ORM entity + * @returns {object} an object containing the bbid and list of ralationship data of an ORM entity + * The relationships property defaults to an empty array. + * If entity is null or undefined, returns null. + * + * @example + * const edition = await orm.func.entity.getEntity(orm, 'Edition', bbid, relations); + * getEntityRelationships(edition); + * /* => { + "bbid": "442ab642-985a-4957-9d61-8a1d9e82de1f", + "relationships": [ + { + "direction": "backward", + "id": 2643, + "linkPhrase": "published by", + "relationshipTypeId": 4, + "relationshipTypeName": "Publisher", + "targetBbid": "aeb93857-6d00-4494-9a7e-ee643dc0bf4d", + "targetEntityType": "Publisher" + } + ] + } + */ + +export function getEntityRelationships(entity: object) { + return _.isNil(entity) ? null : + { + bbid: _.get(entity, 'bbid', null), + relationships: _.get(entity, 'relationshipSet.relationships', []).map((relationship) => { + const isItSourceEntity = entity.bbid === relationship.sourceBbid; + return { + direction: isItSourceEntity ? 'forward' : 'backward', + id: relationship.id, + linkPhrase: isItSourceEntity ? + _.get(relationship, 'type.linkPhrase', null) : + _.get(relationship, 'type.reverseLinkPhrase', null), + relationshipTypeId: _.get(relationship, 'type.id', null), + relationshipTypeName: _.get(relationship, 'type.label', null), + targetBbid: isItSourceEntity ? + _.get(relationship, 'targetBbid', null) : + _.get(relationship, 'sourceBbid', null), + targetEntityType: isItSourceEntity ? + _.kebabCase(_.get(relationship, 'type.targetEntityType', null)) : + _.kebabCase(_.get(relationship, 'type.sourceEntityType', null)) + }; + }) + }; +} diff --git a/src/api/helpers/utils.js b/src/api/helpers/utils.js new file mode 100644 index 0000000000..1746c69a1a --- /dev/null +++ b/src/api/helpers/utils.js @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2019 Akhilesh Kumar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +export const aliasesRelations = ['aliasSet.aliases.language']; +export const identifiersRelations = ['identifierSet.identifiers.type']; +export const relationshipsRelations = ['relationshipSet.relationships.type']; + +/** + * allowOnlyGetMethod is function to allow api to send response only for get requests + * + * @param {object} req - req is an object containing information about the HTTP request + * @param {object} res - res to send back the desired HTTP response + * @param {function} next - this is a callback + * @returns {object} - return to endpoint if request type is GET otherwise respond error with status code 405 + * @example + * + * allowOnlyGetMethod(req, res, next) + */ + +export function allowOnlyGetMethod(req, res, next) { + if (req.method === 'GET') { + return next(); + } + return res.set('Allow', 'GET') + .status(405) + .send({message: `${req.method} method for the "${req.path}" route is not supported. Only GET method is allowed`}); +} diff --git a/src/api/routes.js b/src/api/routes.js new file mode 100644 index 0000000000..f59e479be8 --- /dev/null +++ b/src/api/routes.js @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2019 Akhilesh Kumar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import authorRouter from './routes/author'; +import editionGroupRouter from './routes/edition-group'; +import editionRouter from './routes/edition'; +import publisherRouter from './routes/publisher'; +import workRouter from './routes/work'; + + +/** + *@swagger + *definitions: + * Alias: + * type: object + * properties: + * name: + * type: string + * example: 'Robert A. Heinlein' + * sortName: + * type: string + * example: 'Heinlein, Robert A.' + * aliasLanguage: + * type: string + * example: 'English' + * primary: + * type: boolean + * example: true + * Aliases: + * type: object + * properties: + * bbid: + * type: string + * format: uuid + * example: '2e5f49a8-6a38-4cc7-97c7-8e624e1fc2c1' + * aliases: + * type: array + * items: + * $ref: '#/definitions/Alias' + * Identifiers: + * type: object + * properties: + * bbid: + * type: string + * format: uuid + * example: '2e5f49a8-6a38-4cc7-97c7-8e624e1fc2c1' + * identifiers: + * type: array + * items: + * type: object + * properties: + * type: + * type: string + * example: 'Wikidata ID' + * value: + * type: string + * example: 'Q123078' + * Relationships: + * type: object + * properties: + * bbid: + * type: string + * format: uuid + * example: '2e5f49a8-6a38-4cc7-97c7-8e624e1fc2c1' + * relationships: + * type: array + * items: + * type: object + * properties: + * direction: + * type: string + * example: 'forward' + * id: + * type: number + * example: 804 + * linkPhrase: + * type: string + * example: 'wrote' + * relationshipTypeId: + * type: number + * example: 8 + * relationshipTypeName: + * type: string + * example: 'Author' + * targetBbid: + * type: string + * format: uuid + * example: '4682cf09-66fb-4542-b457-b889117e0279' + * targetEntityType: + * type: string + * example: 'work' + */ + +function initWorkRoute(app) { + app.use('/work', workRouter); +} + +function initEditionRoute(app) { + app.use('/edition', editionRouter); +} + +function initEditionGroupRoute(app) { + app.use('/edition-group', editionGroupRouter); +} + +function initAuthorRoute(app) { + app.use('/author', authorRouter); +} + +function initPublishetRouter(app) { + app.use('/publisher', publisherRouter); +} + + +function initRoutes(app) { + initWorkRoute(app); + initEditionRoute(app); + initEditionGroupRoute(app); + initAuthorRoute(app); + initPublishetRouter(app); +} + +export default initRoutes; diff --git a/src/api/routes/author.js b/src/api/routes/author.js new file mode 100644 index 0000000000..1d3fd87989 --- /dev/null +++ b/src/api/routes/author.js @@ -0,0 +1,219 @@ +/* + * Copyright (C) 2019 Akhilesh Kumar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import {aliasesRelations, identifiersRelations, relationshipsRelations} from '../helpers/utils'; +import {getAuthorBasicInfo, getEntityAliases, getEntityIdentifiers, getEntityRelationships} from '../helpers/formatEntityData'; +import _ from 'lodash'; +import express from 'express'; +import {makeEntityLoader} from '../helpers/entityLoader'; + + +const router = express.Router(); + +const authorBasicRelations = [ + 'defaultAlias.language', + 'disambiguation', + 'authorType', + 'gender', + 'beginArea', + 'endArea' +]; + +const authorError = 'Author not found'; + +/** + *@swagger + *definitions: + * AuthorDetail: + * type: object + * properties: + * bbid: + * type: string + * format: uuid + * example: '2e5f49a8-6a38-4cc7-97c7-8e624e1fc2c1' + * beginArea: + * type: string + * example: 'United States' + * beginDate: + * type: string + * example: '1907-07-07' + * defaultAlias: + * $ref: '#/definitions/Alias' + * disambiguation: + * type: string + * example: 'Robert A. Heinlein' + * endArea: + * type: string + * example: 'United States' + * endDate: + * type: string + * example: '1988-05-08' + * ended: + * type: boolean + * example: true + * gender: + * type: string + * example: 'Male' + * type: + * type: string + * example: 'Person' + * + */ + + +/** + *@swagger + *'/author/{bbid}': + * get: + * tags: + * - Lookup Requests + * summary: Lookup Author by BBID + * description: Returns the basic details of an Author + * operationId: getAuthorByBbid + * produces: + * - application/json + * parameters: + * - name: bbid + * in: path + * description: BBID of the Author + * required: true + * type: string + * responses: + * 200: + * description: Basic information of an Author entity + * schema: + * $ref: '#/definitions/AuthorDetail' + * 404: + * description: Author not found + * 406: + * description: Invalid BBID + */ + +router.get('/:bbid', + makeEntityLoader('Author', authorBasicRelations, authorError), + async (req, res, next) => { + const authorBasicInfo = await getAuthorBasicInfo(res.locals.entity); + return res.status(200).send(authorBasicInfo); + }); + + +/** + * @swagger + * '/author/{bbid}/aliases': + * get: + * tags: + * - Lookup Requests + * summary: Get list of aliases of an Author by BBID + * description: Returns the list of aliases of an Author + * operationId: getAliasesOfAuthorByBbid + * produces: + * - application/json + * parameters: + * - name: bbid + * in: path + * description: BBID of the Author + * required: true + * type: string + * responses: + * 200: + * description: List of aliases with BBID of an Author entity + * schema: + * $ref: '#/definitions/Aliases' + * 404: + * description: Author not found + * 406: + * description: Invalid BBID + */ + +router.get('/:bbid/aliases', + makeEntityLoader('Author', aliasesRelations, authorError), + async (req, res, next) => { + const authorAliasesList = await getEntityAliases(res.locals.entity); + return res.status(200).send(authorAliasesList); + }); + +/** + * @swagger + * '/author/{bbid}/identifiers': + * get: + * tags: + * - Lookup Requests + * summary: Get list of identifiers of an Author by BBID + * description: Returns the list of identifiers of an Author + * operationId: getIdentifiersOfAuthorByBbid + * produces: + * - application/json + * parameters: + * - name: bbid + * in: path + * description: BBID of the Author + * required: true + * type: string + * responses: + * 200: + * description: List of identifiers with BBID of an Author entity + * schema: + * $ref: '#/definitions/Identifiers' + * 404: + * description: Author not found + * 406: + * description: Invalid BBID + */ +router.get('/:bbid/identifiers', + makeEntityLoader('Author', identifiersRelations, authorError), + async (req, res, next) => { + const authorIdentifiersList = await getEntityIdentifiers(res.locals.entity); + return res.status(200).send(authorIdentifiersList); + }); + +/** + * @swagger + * '/author/{bbid}/relationships': + * get: + * tags: + * - Lookup Requests + * summary: Get list of relationships of an Author by BBID + * description: Returns the list of relationships of an Author + * operationId: getRelationshipsOfAuthorByBbid + * produces: + * - application/json + * parameters: + * - name: bbid + * in: path + * description: BBID of the Author + * required: true + * type: string + * responses: + * 200: + * description: List of relationships with BBID of an Author entity + * schema: + * $ref: '#/definitions/Relationships' + * 404: + * description: Author not found + * 406: + * description: Invalid BBID + */ + +router.get('/:bbid/relationships', + makeEntityLoader('Author', relationshipsRelations, authorError), + async (req, res, next) => { + const authorRelationshipList = await getEntityRelationships(res.locals.entity); + return res.status(200).send(authorRelationshipList); + }); + +export default router; diff --git a/src/api/routes/edition-group.js b/src/api/routes/edition-group.js new file mode 100644 index 0000000000..c8682e9fd8 --- /dev/null +++ b/src/api/routes/edition-group.js @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2019 Akhilesh Kumar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import {aliasesRelations, identifiersRelations, relationshipsRelations} from '../helpers/utils'; +import {getEditionGroupBasicInfo, getEntityAliases, getEntityIdentifiers, getEntityRelationships} from '../helpers/formatEntityData'; +import _ from 'lodash'; +import express from 'express'; +import {makeEntityLoader} from '../helpers/entityLoader'; + + +const router = express.Router(); + +const editionGroupBasicRelations = [ + 'defaultAlias.language', + 'disambiguation', + 'editionGroupType' +]; + +const editionGroupError = 'Edition Group not found'; + +/** + *@swagger + *definitions: + * EditionGroupDetail: + * type: object + * properties: + * bbid: + * type: string + * format: uuid + * example: 'ba446064-90a5-447b-abe5-139be547da2e' + * defaultAlias: + * $ref: '#/definitions/Alias' + * disambiguation: + * type: string + * example: 'Harry Porter' + * type: + * type: string + * example: 'Book' + * + */ + +/** + *@swagger + * '/edition-group/{bbid}': + * get: + * tags: + * - Lookup Requests + * summary: Lookup Edition Group by bbid + * description: Returns the basic details of an Edition Group + * operationId: getEditionGroupByBbid + * produces: + * - application/json + * parameters: + * - name: bbid + * in: path + * description: BBID of the Edition Group + * required: true + * type: string + * responses: + * 200: + * description: Basic information of an Edition Group entity + * schema: + * $ref: '#/definitions/EditionGroupDetail' + * 404: + * description: Edition Group not found + * 406: + * description: Invalid BBID + */ + +router.get('/:bbid', + makeEntityLoader('EditionGroup', editionGroupBasicRelations, editionGroupError), + async (req, res, next) => { + const editionGroupBasicInfo = await getEditionGroupBasicInfo(res.locals.entity); + return res.status(200).send(editionGroupBasicInfo); + }); + +/** + * @swagger + * '/edition-group/{bbid}/aliases': + * get: + * tags: + * - Lookup Requests + * summary: Get list of aliases of the Edition Group by BBID + * description: Returns the list of aliases of the Edition Group + * operationId: getAliasesOfEditionGroupByBbid + * produces: + * - application/json + * parameters: + * - name: bbid + * in: path + * description: BBID of the Edition Group + * required: true + * type: string + * responses: + * 200: + * description: List of aliases with BBID of the Edition Group entity + * schema: + * $ref: '#/definitions/Aliases' + * 404: + * description: Edition Group not found + * 406: + * description: Invalid BBID + */ + +router.get('/:bbid/aliases', + makeEntityLoader('EditionGroup', aliasesRelations, editionGroupError), + async (req, res, next) => { + const editionGroupAliasesList = await getEntityAliases(res.locals.entity); + return res.status(200).send(editionGroupAliasesList); + }); + +/** + * @swagger + * '/edition-group/{bbid}/identifiers': + * get: + * tags: + * - Lookup Requests + * summary: Get list of identifiers of an Edition Group by BBID + * description: Returns the list of identifiers of an Edition Group + * operationId: getIdentifiersOfEditionGroupByBbid + * produces: + * - application/json + * parameters: + * - name: bbid + * in: path + * description: BBID of the Edition Group + * required: true + * type: string + * responses: + * 200: + * description: List of identifiers with BBID of an Edition Group entity + * schema: + * $ref: '#/definitions/Identifiers' + * 404: + * description: Edition Group not found + * 406: + * description: Invalid BBID + */ + +router.get('/:bbid/identifiers', + makeEntityLoader('EditionGroup', identifiersRelations, editionGroupError), + async (req, res, next) => { + const editionGroupIdentifiersList = await getEntityIdentifiers(res.locals.entity); + return res.status(200).send(editionGroupIdentifiersList); + }); + + +/** + * @swagger + * '/edition-group/{bbid}/relationships': + * get: + * tags: + * - Lookup Requests + * summary: Get list of relationships of an Edition Group by BBID + * description: Returns the list of relationships of an Edition Group + * operationId: getRelationshipsOfEditionGroupByBbid + * produces: + * - application/json + * parameters: + * - name: bbid + * in: path + * description: BBID of the Edition Group + * required: true + * type: string + * responses: + * 200: + * description: List of relationships with BBID of an Edition Group entity + * schema: + * $ref: '#/definitions/Relationships' + * 404: + * description: Edition Group not found + * 406: + * description: Invalid BBID + */ + +router.get('/:bbid/relationships', + makeEntityLoader('EditionGroup', relationshipsRelations, editionGroupError), + async (req, res, next) => { + const editionGroupRelationshipList = await getEntityRelationships(res.locals.entity); + return res.status(200).send(editionGroupRelationshipList); + }); + +export default router; diff --git a/src/api/routes/edition.js b/src/api/routes/edition.js new file mode 100644 index 0000000000..6fe1fd803f --- /dev/null +++ b/src/api/routes/edition.js @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2019 Akhilesh Kumar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import {aliasesRelations, identifiersRelations, relationshipsRelations} from '../helpers/utils'; +import {getEditionBasicInfo, getEntityAliases, getEntityIdentifiers, getEntityRelationships} from '../helpers/formatEntityData'; +import _ from 'lodash'; +import express from 'express'; +import {makeEntityLoader} from '../helpers/entityLoader'; + + +const router = express.Router(); + +const editionBasicRelations = [ + 'defaultAlias.language', + 'languageSet.languages', + 'disambiguation', + 'editionFormat', + 'editionStatus', + 'releaseEventSet.releaseEvents' +]; + +const editionError = 'Edition not found'; + +/** + *@swagger + *definitions: + * EditionDetail: + * type: object + * properties: + * bbid: + * type: string + * format: uuid + * example: '96a23368-85a1-4559-b3df-16833893d861' + * defaultAlias: + * $ref: '#/definitions/Alias' + * depth: + * type: integer + * description: 'depth in mm' + * example: 40 + * disambiguation: + * type: string + * example: 'Limited edition boxed set' + * editionFormat: + * type: string + * example: 'eBook' + * height: + * type: integer + * description: 'height in mm' + * example: 250 + * languages: + * type: array + * items: + * type: string + * example: 'English' + * pages: + * type: integer + * example: 200 + * releaseEventDates: + * type: string + * example: '2011-01-01' + * status: + * type: string + * example: 'Official' + * weight: + * type: integer + * description: 'Weight in grams' + * example: 300 + * width: + * type: integer + * description: 'width in mm' + * example: 80 + * + */ + + +/** + * + * @swagger + * '/edition/{bbid}': + * get: + * tags: + * - Lookup Requests + * summary: Lookup Edition by BBID + * description: Returns the basic details of an Edition + * operationId: getEditionByBbid + * produces: + * - application/json + * parameters: + * - name: bbid + * in: path + * description: BBID of the Edition + * required: true + * type: string + * responses: + * 200: + * description: Basic information of an Edition entity + * schema: + * $ref: '#/definitions/EditionDetail' + * 404: + * description: Edition not found + * 406: + * description: Invalid BBID + */ + + +router.get('/:bbid', + makeEntityLoader('Edition', editionBasicRelations, editionError), + async (req, res, next) => { + const editionBasicInfo = await getEditionBasicInfo(res.locals.entity); + return res.status(200).send(editionBasicInfo); + }); + +/** + * @swagger + * '/edition/{bbid}/aliases': + * get: + * tags: + * - Lookup Requests + * summary: Get list of aliases of the Edition by BBID + * description: Returns the list of aliases of the Edition + * operationId: getAliasesOfEditionByBbid + * produces: + * - application/json + * parameters: + * - name: bbid + * in: path + * description: BBID of the Edition + * required: true + * type: string + * responses: + * 200: + * description: List of aliases with BBID of the Edition entity + * schema: + * $ref: '#/definitions/Aliases' + * 404: + * description: Edition not found + * 406: + * description: Invalid BBID + */ + +router.get('/:bbid/aliases', + makeEntityLoader('Edition', aliasesRelations, editionError), + async (req, res, next) => { + const editionAliasesList = await getEntityAliases(res.locals.entity); + return res.status(200).send(editionAliasesList); + }); + +/** + * @swagger + * '/edition/{bbid}/identifiers': + * get: + * tags: + * - Lookup Requests + * summary: Get list of identifiers of an Edition by BBID + * description: Returns the list of identifiers of an Edition + * operationId: getIdentifiersOfEditionByBbid + * produces: + * - application/json + * parameters: + * - name: bbid + * in: path + * description: BBID of the Edition + * required: true + * type: string + * responses: + * 200: + * description: List of identifiers with BBID of an Edition entity + * schema: + * $ref: '#/definitions/Identifiers' + * 404: + * description: Edition not found + * 406: + * description: Invalid BBID + */ + +router.get('/:bbid/identifiers', + makeEntityLoader('Edition', identifiersRelations, editionError), + async (req, res, next) => { + const editionIdentifiersList = await getEntityIdentifiers(res.locals.entity); + return res.status(200).send(editionIdentifiersList); + }); + +/** + * @swagger + * '/edition/{bbid}/relationships': + * get: + * tags: + * - Lookup Requests + * summary: Get list of relationships of an Edition by BBID + * description: Returns the list of relationships of an Edition + * operationId: getRelationshipsOfEditionByBbid + * produces: + * - application/json + * parameters: + * - name: bbid + * in: path + * description: BBID of the Edition + * required: true + * type: string + * responses: + * 200: + * description: List of relationships with BBID of an Edition entity + * schema: + * $ref: '#/definitions/Relationships' + * 404: + * description: Edition not found + * 406: + * description: Invalid BBID + */ + +router.get('/:bbid/relationships', + makeEntityLoader('Edition', relationshipsRelations, editionError), + async (req, res, next) => { + const editionRelationshipList = await getEntityRelationships(res.locals.entity); + return res.status(200).send(editionRelationshipList); + }); + +export default router; diff --git a/src/api/routes/publisher.js b/src/api/routes/publisher.js new file mode 100644 index 0000000000..79bbef78f7 --- /dev/null +++ b/src/api/routes/publisher.js @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2019 Akhilesh Kumar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import {aliasesRelations, identifiersRelations, relationshipsRelations} from '../helpers/utils'; +import {getEntityAliases, getEntityIdentifiers, getEntityRelationships, getPublisherBasicInfo} from '../helpers/formatEntityData'; +import _ from 'lodash'; +import express from 'express'; +import {makeEntityLoader} from '../helpers/entityLoader'; + + +const router = express.Router(); + +const publisherBasicRelations = [ + 'defaultAlias.language', + 'disambiguation', + 'publisherType', + 'area' +]; + +const publisherError = 'Publisher not found'; + +/** + *@swagger + *definitions: + * PublisherDetail: + * type: object + * properties: + * bbid: + * type: string + * format: uuid + * example: 'e418874e-5684-4fe9-9d2d-1b7e5d43fd59' + * area: + * type: string + * example: 'India' + * beginDate: + * type: string + * example: '1943' + * defaultAlias: + * $ref: '#/definitions/Alias' + * disambiguation: + * type: string + * example: 'Bharati Bhawan' + * endDate: + * type: string + * example: + * ended: + * type: boolean + * example: false + * gender: + * type: string + * example: 'Male' + * type: + * type: string + * example: 'Publisher' + */ + + +/** + * + *@swagger + * '/publisher/{bbid}': + * get: + * tags: + * - Lookup Requests + * summary: Lookup Publisher by BBID + * description: Returns the basic details of the Publisher + * operationId: getPublisherByBbid + * produces: + * - application/json + * parameters: + * - name: bbid + * in: path + * description: BBID of the Publisher + * required: true + * type: string + * responses: + * 200: + * description: Basic information of the Publisher entity + * schema: + * $ref: '#/definitions/PublisherDetail' + * 404: + * description: Publisher not found + * 406: + * description: Invalid BBID + */ + +router.get('/:bbid', + makeEntityLoader('Publisher', publisherBasicRelations, publisherError), + async (req, res, next) => { + const publisherBasicInfo = await getPublisherBasicInfo(res.locals.entity); + return res.status(200).send(publisherBasicInfo); + }); + +/** + *@swagger + * '/publisher/{bbid}/aliases': + * get: + * tags: + * - Lookup Requests + * summary: Get list of aliases of the Publisher by BBID + * description: Returns the list of aliases of the Publisher + * operationId: getAliasesOfPublisherByBbid + * produces: + * - application/json + * parameters: + * - name: bbid + * in: path + * description: BBID of the Publisher + * required: true + * type: string + * responses: + * 200: + * description: List of aliases with BBID of the Publisher + * schema: + * $ref: '#/definitions/Aliases' + * 404: + * description: Publisher not found + * 406: + * description: Invalid BBID + */ +router.get('/:bbid/aliases', + makeEntityLoader('Publisher', aliasesRelations, publisherError), + async (req, res, next) => { + const publisherAliasesList = await getEntityAliases(res.locals.entity); + return res.status(200).send(publisherAliasesList); + }); + +/** + * @swagger + * '/publisher/{bbid}/identifiers': + * get: + * tags: + * - Lookup Requests + * summary: Get list of identifiers of the Publisher by BBID + * description: Returns the list of identifiers of the Publisher + * operationId: getIdentifiersOfPublisherByBbid + * produces: + * - application/json + * parameters: + * - name: bbid + * in: path + * description: BBID of the Publisher + * required: true + * type: string + * responses: + * 200: + * description: List of identifiers with BBID of the Publisher entity + * schema: + * $ref: '#/definitions/Identifiers' + * 404: + * description: Publisher not found + * 406: + * description: Invalid BBID + */ + +router.get('/:bbid/identifiers', + makeEntityLoader('Publisher', identifiersRelations, publisherError), + async (req, res, next) => { + const publisherIdentifiersList = await getEntityIdentifiers(res.locals.entity); + return res.status(200).send(publisherIdentifiersList); + }); + +/** + * @swagger + * '/publisher/{bbid}/relationships': + * get: + * tags: + * - Lookup Requests + * summary: Get list of relationships of the Publisher by BBID + * description: Returns the list of relationships of the Publisher + * operationId: getRelationshipsOfPublisherByBbid + * produces: + * - application/json + * parameters: + * - name: bbid + * in: path + * description: BBID of the Publisher + * required: true + * type: string + * responses: + * 200: + * description: List of relationships with BBID of the Publisher entity + * schema: + * $ref: '#/definitions/Relationships' + * 404: + * description: Publisher not found + * 406: + * description: Invalid BBID + */ + +router.get('/:bbid/relationships', + makeEntityLoader('Publisher', relationshipsRelations, publisherError), + async (req, res, next) => { + const publisherRelationshipList = await getEntityRelationships(res.locals.entity); + return res.status(200).send(publisherRelationshipList); + }); + +export default router; diff --git a/src/api/routes/work.js b/src/api/routes/work.js new file mode 100644 index 0000000000..26bbce4618 --- /dev/null +++ b/src/api/routes/work.js @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2019 Akhilesh Kumar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import {aliasesRelations, identifiersRelations, relationshipsRelations} from '../helpers/utils'; +import {getEntityAliases, getEntityIdentifiers, getEntityRelationships, getWorkBasicInfo} from '../helpers/formatEntityData'; +import _ from 'lodash'; +import express from 'express'; +import {makeEntityLoader} from '../helpers/entityLoader'; + + +const router = express.Router(); + +const workBasicRelations = [ + 'defaultAlias.language', + 'languageSet.languages', + 'disambiguation', + 'workType' +]; + +const workError = 'Work not found'; + +/** + *@swagger + *definitions: + * WorkDetail: + * type: object + * properties: + * bbid: + * type: string + * format: uuid + * example: ba446064-90a5-447b-abe5-139be547da2e + * defaultAlias: + * $ref: '#/definitions/Alias' + * disambiguation: + * type: string + * example: 'Harry Porter 1' + * entityType: + * type: string + * example: 'Work' + * languages: + * type: array + * items: + * type: string + * example: English + * workType: + * type: string + * example: 'Epic' + * + */ + +/** + *@swagger + *'/work/{bbid}': + * get: + * tags: + * - Lookup Requests + * summary: Lookup Work by BBID + * description: Returns the basic details of the Work + * operationId: getWorkByBbid + * produces: + * - application/json + * parameters: + * - name: bbid + * in: path + * description: BBID of the Work + * required: true + * type: string + * responses: + * 200: + * description: Basic information of the Work entity + * schema: + * $ref: '#/definitions/WorkDetail' + * 404: + * description: Work not found + * 406: + * description: Invalid BBID + */ + +router.get('/:bbid', + makeEntityLoader('Work', workBasicRelations, workError), + async (req, res, next) => { + const workBasicInfo = await getWorkBasicInfo(res.locals.entity); + return res.status(200).send(workBasicInfo); + }); + + +/** + *@swagger + * '/work/{bbid}/aliases': + * get: + * tags: + * - Lookup Requests + * summary: Get list of aliases of a Work by BBID + * description: Returns the list of aliases of a Work + * operationId: getAliasesOfWorkByBbid + * produces: + * - application/json + * parameters: + * - name: bbid + * in: path + * description: BBID of the Work + * required: true + * type: string + * responses: + * 200: + * description: List of aliases with BBID of a Work entity + * schema: + * $ref: '#/definitions/Aliases' + * 404: + * description: Work not found + * 406: + * description: Invalid BBID + */ + + +router.get('/:bbid/aliases', + makeEntityLoader('Work', aliasesRelations, workError), + async (req, res, next) => { + const workAliasesList = await getEntityAliases(res.locals.entity); + return res.status(200).send(workAliasesList); + }); + +/** + * @swagger + * '/work/{bbid}/identifiers': + * get: + * tags: + * - Lookup Requests + * summary: Get list of identifiers of the Work by BBID + * description: Returns the list of identifiers of the Work + * operationId: getIdentifiersOfWorkByBbid + * produces: + * - application/json + * parameters: + * - name: bbid + * in: path + * description: BBID of the Work + * required: true + * type: string + * responses: + * 200: + * description: List of identifiers with BBID of the Work entity + * schema: + * $ref: '#/definitions/Identifiers' + * 404: + * description: Work not found + * 406: + * description: Invalid BBID + */ + +router.get('/:bbid/identifiers', + makeEntityLoader('Work', identifiersRelations, workError), + async (req, res, next) => { + const workIdentifiersList = await getEntityIdentifiers(res.locals.entity); + return res.status(200).send(workIdentifiersList); + }); + + +/** + * @swagger + * '/work/{bbid}/relationships': + * get: + * tags: + * - Lookup Requests + * summary: Get list of relationships of the Work by BBID + * description: Returns the list of relationships of the Work + * operationId: getRelationshipsOfWorkByBbid + * produces: + * - application/json + * parameters: + * - name: bbid + * in: path + * description: BBID of the Work + * required: true + * type: string + * responses: + * 200: + * description: List of relationships with BBID of the Work entity + * schema: + * $ref: '#/definitions/Relationships' + * 404: + * description: Work not found + * 406: + * description: Invalid BBID + */ + +router.get('/:bbid/relationships', + makeEntityLoader('Work', relationshipsRelations, workError), + async (req, res, next) => { + const workRelationshipList = await getEntityRelationships(res.locals.entity); + return res.status(200).send(workRelationshipList); + }); + +export default router; diff --git a/src/api/swagger.js b/src/api/swagger.js new file mode 100644 index 0000000000..52a10927a1 --- /dev/null +++ b/src/api/swagger.js @@ -0,0 +1,42 @@ + +/* eslint-disable */ +const express = require('express') +const router = express.Router() + +const swaggerOptions = { + swaggerDefinition: { + info: { + title: 'BookBrainz API Documentation', + version: '0.1.0', + description: 'Swagger 2.0 documentation for the BookBrainz REST API.', + contact: { + email: 'akhileshithcse@gmail.com' + } + }, + schemes: ['http'] + }, + apis: ['src/api/routes/*.js', 'src/api/*.js'] +} + +const swaggerUIOptions = { + swaggerOptions: { + deepLinking: true, + defaultModelsExpandDepth: 3, + defaultModelExpandDepth: 3, + operationsSorter: 'alpha' + } +}; + +import swaggerJSDoc from 'swagger-jsdoc'; +import swaggerUi from 'swagger-ui-express'; +const swaggerSpec = swaggerJSDoc(swaggerOptions); + +router.get('/json', function (req, res) { + res.setHeader('Content-Type', 'application/json') + res.send(swaggerSpec) +}); + +router.use('/', swaggerUi.serve, swaggerUi.setup(swaggerSpec, swaggerUIOptions)); + + +export default router; diff --git a/src/server/helpers/appCleanup.js b/src/common/helpers/appCleanup.js similarity index 100% rename from src/server/helpers/appCleanup.js rename to src/common/helpers/appCleanup.js diff --git a/src/server/helpers/config.js b/src/common/helpers/config.js similarity index 100% rename from src/server/helpers/config.js rename to src/common/helpers/config.js diff --git a/src/common/helpers/error.js b/src/common/helpers/error.js new file mode 100644 index 0000000000..f901d8ba96 --- /dev/null +++ b/src/common/helpers/error.js @@ -0,0 +1,163 @@ +/* + * Copyright (C) 2015-2016 Sean Burke + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import Log from 'log'; +import config from '../../common/helpers/config'; +import status from 'http-status'; + + +const log = new Log(config.site.log); + +export class SiteError extends Error { + constructor(message) { + super(); + + /* + * We can't access the subclass's default message before calling super, + * so we set it manually here + */ + this.message = message || this.constructor.defaultMessage; + + this.name = this.constructor.name; + this.status = this.constructor.status; + } + + static get defaultMessage() { + return 'An unhandled error occurred'; + } + + static get status() { + return status.INTERNAL_SERVER_ERROR; + } +} + +class PathError extends SiteError { + constructor(message, req) { + super(message); + this.detailedMessage = this.constructor.detailedMessage && + this.constructor.detailedMessage(req); + } +} + +class _AuthenticationError extends SiteError { + static get status() { + return status.UNAUTHORIZED; + } +} + +export class AuthenticationFailedError extends _AuthenticationError { + static get defaultMessage() { + return 'Invalid authentication credentials'; + } +} + +// For use when something slips past client-side validation +export class FormSubmissionError extends SiteError { + static get defaultMessage() { + return 'Form contained invalid data'; + } + + static get status() { + return status.BAD_REQUEST; + } +} + +export class NotAuthenticatedError extends _AuthenticationError { + static get defaultMessage() { + return 'You are not currently authenticated'; + } +} + +export class NotFoundError extends PathError { + static get defaultMessage() { + return 'Page not found'; + } + + static get status() { + return status.NOT_FOUND; + } + + static detailedMessage(req) { + return [ + `No content exists at the path requested: ${req.path}`, + 'Please make sure you have entered in the correct address!' + ]; + } +} + +export class PermissionDeniedError extends PathError { + static get defaultMessage() { + return 'You do not have permission to access this page'; + } + + static get status() { + return status.FORBIDDEN; + } + + static detailedMessage(req) { + return [ + `You do not have permission to access the following path: + ${req.path}`, + `Please make sure you have entered in the correct credentials and + address!` + ]; + } +} + +function _logError(err) { + log.error(err); +} + +export function getErrorToSend(err) { + if (err instanceof SiteError) { + return err; + } + + /* + * If we haven't generated the error ourselves with display in mind, log + * instead and return a new generic SiteError + */ + _logError(err); + return new SiteError(); +} + +export function sendErrorAsJSON(res, err) { + const errorToSend = getErrorToSend(err); + + res.status( + errorToSend.status || status.INTERNAL_SERVER_ERROR + ).send({error: errorToSend.message}); +} + +export class AwardNotUnlockedError extends Error { + constructor(message) { + super(); + + /* + * We can't access the subclass's default message before calling super, + * so we set it manually here + */ + this.message = message || this.constructor.defaultMessage; + + this.name = this.constructor.name; + } + + static get defaultMessage() { + return 'An award was not unlocked'; + } +} diff --git a/src/common/helpers/utils.js b/src/common/helpers/utils.js new file mode 100644 index 0000000000..80a76b27dd --- /dev/null +++ b/src/common/helpers/utils.js @@ -0,0 +1,21 @@ + +// @flow + +/** + * Regular expression for valid BookBrainz UUIDs (bbid) + * + * @type {RegExp} + * @private + */ +const _bbidRegex = + /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/; + +/** + * Tests if a BookBrainz UUID is valid + * + * @param {string} bbid - BookBrainz UUID to validate + * @returns {boolean} - Returns true if BookBrainz UUID is valid + */ +export function isValidBBID(bbid: string): boolean { + return _bbidRegex.test(bbid); +} diff --git a/src/server/app.js b/src/server/app.js index fbb887d9e0..b7f97c3fc9 100644 --- a/src/server/app.js +++ b/src/server/app.js @@ -20,17 +20,17 @@ /* eslint global-require: 'warn' */ import * as auth from './helpers/auth'; -import * as error from './helpers/error'; +import * as error from '../common/helpers/error'; import * as search from './helpers/search'; - +import * as serverErrorHelper from './helpers/error'; import BookBrainzData from 'bookbrainz-data'; import Debug from 'debug'; import Promise from 'bluebird'; import {get as _get} from 'lodash'; -import appCleanup from './helpers/appCleanup'; +import appCleanup from '../common/helpers/appCleanup'; import bodyParser from 'body-parser'; import compression from 'compression'; -import config from './helpers/config'; +import config from '../common/helpers/config'; import express from 'express'; import favicon from 'serve-favicon'; import git from 'git-rev'; @@ -155,7 +155,7 @@ app.use((req, res, next) => { // Error handler; arity MUST be 4 or express doesn't treat it as such app.use((err, req, res, next) => { // eslint-disable-line no-unused-vars - error.renderError(req, res, err); + serverErrorHelper.renderError(req, res, err); }); const debug = Debug('bbsite'); diff --git a/src/server/helpers/achievement.js b/src/server/helpers/achievement.js index 730399e643..529bc7cf68 100644 --- a/src/server/helpers/achievement.js +++ b/src/server/helpers/achievement.js @@ -22,11 +22,11 @@ */ /* eslint prefer-spread: 1, prefer-reflect: 1, no-magic-numbers: 0 */ -import * as error from './error'; +import * as error from '../../common/helpers/error'; import Log from 'log'; import Promise from 'bluebird'; -import config from './config'; +import config from '../../common/helpers/config'; const log = new Log(config.site.log); diff --git a/src/server/helpers/auth.js b/src/server/helpers/auth.js index 7b09987c4a..1331cb036a 100644 --- a/src/server/helpers/auth.js +++ b/src/server/helpers/auth.js @@ -18,11 +18,11 @@ */ import * as MusicBrainzOAuth from 'passport-musicbrainz-oauth2'; -import * as error from '../helpers/error'; +import * as error from '../../common/helpers/error'; import Log from 'log'; import _ from 'lodash'; -import config from './config'; +import config from '../../common/helpers/config'; import passport from 'passport'; import status from 'http-status'; diff --git a/src/server/helpers/entityRouteUtils.js b/src/server/helpers/entityRouteUtils.js index 919c0363cc..6624fbb239 100644 --- a/src/server/helpers/entityRouteUtils.js +++ b/src/server/helpers/entityRouteUtils.js @@ -21,7 +21,7 @@ import * as Immutable from 'immutable'; import * as entityEditorHelpers from '../../client/entity-editor/helpers'; import * as entityRoutes from '../routes/entity/entity'; -import * as error from './error'; +import * as error from '../../common/helpers/error'; import * as propHelpers from '../../client/helpers/props'; import * as utils from './utils'; diff --git a/src/server/helpers/error.js b/src/server/helpers/error.js index 82784dad53..7ec60cfd19 100644 --- a/src/server/helpers/error.js +++ b/src/server/helpers/error.js @@ -16,135 +16,18 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +import * as error from '../../common/helpers/error'; import * as propHelpers from '../../client/helpers/props'; import ErrorPage from '../../client/components/pages/error'; import Layout from '../../client/containers/layout'; -import Log from 'log'; import React from 'react'; import ReactDOMServer from 'react-dom/server'; -import config from './config'; import {generateProps} from './props'; import status from 'http-status'; import target from '../templates/target'; - -const log = new Log(config.site.log); - -export class SiteError extends Error { - constructor(message) { - super(); - - /* - * We can't access the subclass's default message before calling super, - * so we set it manually here - */ - this.message = message || this.constructor.defaultMessage; - - this.name = this.constructor.name; - this.status = this.constructor.status; - } - - static get defaultMessage() { - return 'An unhandled error occurred'; - } - - static get status() { - return status.INTERNAL_SERVER_ERROR; - } -} - -class PathError extends SiteError { - constructor(message, req) { - super(message); - this.detailedMessage = this.constructor.detailedMessage && - this.constructor.detailedMessage(req); - } -} - -class _AuthenticationError extends SiteError { - static get status() { - return status.UNAUTHORIZED; - } -} - -export class AuthenticationFailedError extends _AuthenticationError { - static get defaultMessage() { - return 'Invalid authentication credentials'; - } -} - -// For use when something slips past client-side validation -export class FormSubmissionError extends SiteError { - static get defaultMessage() { - return 'Form contained invalid data'; - } - - static get status() { - return status.BAD_REQUEST; - } -} - -export class NotAuthenticatedError extends _AuthenticationError { - static get defaultMessage() { - return 'You are not currently authenticated'; - } -} - -export class NotFoundError extends PathError { - static get defaultMessage() { - return 'Page not found'; - } - - static get status() { - return status.NOT_FOUND; - } - - static detailedMessage(req) { - return [ - `No content exists at the path requested: ${req.path}`, - 'Please make sure you have entered in the correct address!' - ]; - } -} - -export class PermissionDeniedError extends PathError { - static get defaultMessage() { - return 'You do not have permission to access this page'; - } - - static get status() { - return status.FORBIDDEN; - } - - static detailedMessage(req) { - return [ - `You do not have permission to access the following path: - ${req.path}`, - `Please make sure you have entered in the correct credentials and - address!` - ]; - } -} - -function _logError(err) { - log.error(err); -} - -function _getErrorToSend(err) { - if (err instanceof SiteError) { - return err; - } - - /* - * If we haven't generated the error ourselves with display in mind, log - * instead and return a new generic SiteError - */ - _logError(err); - return new SiteError(); -} - export function renderError(req, res, err) { - const errorToSend = _getErrorToSend(err); + const errorToSend = error.getErrorToSend(err); const props = generateProps(req, res, { error: errorToSend }); @@ -159,29 +42,3 @@ export function renderError(req, res, err) { errorToSend.status || status.INTERNAL_SERVER_ERROR ).send(target({markup})); } - -export function sendErrorAsJSON(res, err) { - const errorToSend = _getErrorToSend(err); - - res.status( - errorToSend.status || status.INTERNAL_SERVER_ERROR - ).send({error: errorToSend.message}); -} - -export class AwardNotUnlockedError extends Error { - constructor(message) { - super(); - - /* - * We can't access the subclass's default message before calling super, - * so we set it manually here - */ - this.message = message || this.constructor.defaultMessage; - - this.name = this.constructor.name; - } - - static get defaultMessage() { - return 'An award was not unlocked'; - } -} diff --git a/src/server/helpers/handler.js b/src/server/helpers/handler.js index cff8a12217..a4e85046a7 100644 --- a/src/server/helpers/handler.js +++ b/src/server/helpers/handler.js @@ -17,7 +17,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -import * as error from './error'; +import * as error from '../../common/helpers/error'; export function sendPromiseResult(res, promise, processingCallback) { diff --git a/src/server/helpers/middleware.js b/src/server/helpers/middleware.js index 9e7ac71917..52afa10bfe 100644 --- a/src/server/helpers/middleware.js +++ b/src/server/helpers/middleware.js @@ -17,12 +17,14 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -import * as error from '../helpers/error'; +import * as commonUtils from '../../common/helpers/utils'; +import * as error from '../../common/helpers/error'; import * as utils from '../helpers/utils'; import Promise from 'bluebird'; import renderRelationship from '../helpers/render'; + function makeLoader(modelName, propName, sortFunc) { return function loaderFunc(req, res, next) { const model = req.app.locals.orm[modelName]; @@ -141,7 +143,7 @@ export function makeEntityLoader(modelName, additionalRels, errMessage) { return async (req, res, next, bbid) => { const {orm} = req.app.locals; - if (utils.isValidBBID(bbid)) { + if (commonUtils.isValidBBID(bbid)) { try { const entity = await orm.func.entity.getEntity(orm, modelName, bbid, relations); if (!entity.dataId) { diff --git a/src/server/helpers/search.js b/src/server/helpers/search.js index d23540f05c..5a5f73292f 100644 --- a/src/server/helpers/search.js +++ b/src/server/helpers/search.js @@ -16,6 +16,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ +import * as commonUtils from '../../common/helpers/utils'; import * as utils from '../helpers/utils'; import ElasticSearch from 'elasticsearch'; @@ -149,7 +150,7 @@ async function _processEntityListForBulk(entityList) { export function autocomplete(orm, query, collection) { let queryBody = null; - if (utils.isValidBBID(query)) { + if (commonUtils.isValidBBID(query)) { queryBody = { ids: { values: [query] diff --git a/src/server/helpers/utils.js b/src/server/helpers/utils.js index 837ca6ec3e..8e22be34e9 100644 --- a/src/server/helpers/utils.js +++ b/src/server/helpers/utils.js @@ -108,25 +108,6 @@ export function getEntityModelByType(orm: Object, type: string): Object { return entityModels[type]; } -/** - * Regular expression for valid BookBrainz UUIDs (bbid) - * - * @type {RegExp} - * @private - */ -const _bbidRegex = - /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/; - -/** - * Tests if a BookBrainz UUID is valid - * - * @param {string} bbid - BookBrainz UUID to validate - * @returns {boolean} - Returns true if BookBrainz UUID is valid - */ -export function isValidBBID(bbid: string): boolean { - return _bbidRegex.test(bbid); -} - /** * Helper-function / template-tag that allows the values of an object that * is passed in at a later time to be interpolated into a diff --git a/src/server/routes.js b/src/server/routes.js index 021960aabe..25d0a456a1 100644 --- a/src/server/routes.js +++ b/src/server/routes.js @@ -30,6 +30,7 @@ import searchRouter from './routes/search'; import statisticsRouter from './routes/statistics'; import workRouter from './routes/entity/work'; + function initRootRoutes(app) { app.use('/', indexRouter); app.use('/', authRouter); @@ -70,7 +71,6 @@ function initEditorRoutes(app) { function initRoutes(app) { initRootRoutes(app); - initEditionGroupRoutes(app); initAuthorRoutes(app); initEditionRoutes(app); diff --git a/src/server/routes/editor.js b/src/server/routes/editor.js index eb97084e46..83dae86d95 100644 --- a/src/server/routes/editor.js +++ b/src/server/routes/editor.js @@ -18,7 +18,7 @@ */ import * as auth from '../helpers/auth'; -import * as error from '../helpers/error'; +import * as error from '../../common/helpers/error'; import * as handler from '../helpers/handler'; import * as propHelpers from '../../client/helpers/props'; import {escapeProps, generateProps} from '../helpers/props'; diff --git a/src/server/routes/entity/entity.js b/src/server/routes/entity/entity.js index 0f17f933b6..05b9ffcd11 100644 --- a/src/server/routes/entity/entity.js +++ b/src/server/routes/entity/entity.js @@ -49,7 +49,7 @@ import React from 'react'; import ReactDOMServer from 'react-dom/server'; import WorkPage from '../../../client/components/pages/entities/work'; import _ from 'lodash'; -import config from '../../helpers/config'; +import config from '../../../common/helpers/config'; import target from '../../templates/target'; type PassportRequest = $Request & {user: any, session: any}; diff --git a/src/server/routes/register.js b/src/server/routes/register.js index b9b1e6987e..a620bc6569 100644 --- a/src/server/routes/register.js +++ b/src/server/routes/register.js @@ -18,7 +18,7 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ -import * as error from '../helpers/error'; +import * as error from '../../common/helpers/error'; import * as handler from '../helpers/handler'; import * as middleware from '../helpers/middleware'; import * as propHelpers from '../../client/helpers/props'; @@ -31,7 +31,7 @@ import RegisterAuthPage from '../../client/components/pages/registration-auth'; import RegisterDetailPage from '../../client/components/forms/registration-details'; import _ from 'lodash'; -import config from '../helpers/config'; +import config from '../../common/helpers/config'; import express from 'express'; import target from '../templates/target'; diff --git a/src/server/routes/search.js b/src/server/routes/search.js index f9ad294497..cd86c86a95 100644 --- a/src/server/routes/search.js +++ b/src/server/routes/search.js @@ -18,7 +18,7 @@ */ import * as auth from '../helpers/auth'; -import * as error from '../helpers/error'; +import * as error from '../../common/helpers/error'; import * as handler from '../helpers/handler'; import * as propHelpers from '../../client/helpers/props'; import * as search from '../helpers/search'; diff --git a/test/mocha.opts b/test/mocha.opts index 03eee00c7f..1b436cceeb 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -1,4 +1,4 @@ --recursive --require babel-register ---timeout 20000 +--timeout 50000 --exit diff --git a/test/src/api/common-tests.js b/test/src/api/common-tests.js new file mode 100644 index 0000000000..fdff941c8d --- /dev/null +++ b/test/src/api/common-tests.js @@ -0,0 +1,135 @@ +/* eslint-disable prefer-arrow-callback,func-names */ +/* + * Copyright (C) 2019 Akhilesh Kumar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import {createAuthor, getRandomUUID, truncateEntities} from '../../test-helpers/create-entities'; +import app from '../../../src/api/app'; +import chai from 'chai'; +import chaiHttp from 'chai-http'; + +chai.use(chaiHttp); +const {expect} = chai; + + +const aBBID = getRandomUUID(); +const bBBID = getRandomUUID(); + +describe('Common test of API', () => { + // Test API for envalid requests + it('should throw a 405 error if send post request', function (done) { + chai.request(app) + .post(`/work/${bBBID}`) + .end(function (err, res) { + if (err) { return done(err); } + expect(res).to.have.status(405); + expect(res.ok).to.be.false; + expect(res.body).to.be.an('object'); + expect(res.body).to.have.all.keys('message'); + return done(); + }); + }); + it('should throw a 405 error if send put request', function (done) { + chai.request(app) + .put(`/work/${bBBID}`) + .end(function (err, res) { + if (err) { return done(err); } + expect(res).to.have.status(405); + expect(res.ok).to.be.false; + expect(res.body).to.be.an('object'); + expect(res.body).to.have.all.keys('message'); + return done(); + }); + }); + it('should throw a 405 error if send delete request', function (done) { + chai.request(app) + .delete(`/work/${bBBID}`) + .end(function (err, res) { + if (err) { return done(err); } + expect(res).to.have.status(405); + expect(res.ok).to.be.false; + expect(res.body).to.be.an('object'); + expect(res.body).to.have.all.keys('message'); + return done(); + }); + }); + + it('should throw a 404 error if endpoint is not valid', function (done) { + chai.request(app) + .get(`/work/${bBBID}/not-valid`) + .end(function (err, res) { + if (err) { return done(err); } + expect(res).to.have.status(404); + expect(res.ok).to.be.false; + expect(res.body).to.be.an('object'); + expect(res.body).to.have.all.keys('message'); + return done(); + }); + }); +}); + +describe('Lookup endpoints', () => { + before(() => createAuthor(aBBID)); + after(truncateEntities); + it('GET {entity}/{BBID}/aliases should return aliases with a specific structure', async function () { + const res = await chai.request(app).get(`/author/${aBBID}/aliases`); + expect(res.body.aliases).to.be.an('array'); + expect(res.body.aliases[0]).to.be.an('object'); + expect(res.body.aliases[0]).to.have.all.keys( + 'aliasLanguage', + 'name', + 'sortName', + 'primary' + ); + expect(res.body.aliases[0].aliasLanguage).to.be.a('string'); + expect(res.body.aliases[0].name).to.be.a('string'); + expect(res.body.aliases[0].sortName).to.be.a('string'); + expect(res.body.aliases[0].primary).to.be.a('boolean'); + }); + + it('GET {entity}/{BBID}/identifiers should return identifiers with a specific structure', async function () { + const res = await chai.request(app).get(`/author/${aBBID}/identifiers`); + expect(res.body.identifiers).to.be.an('array'); + expect(res.body.identifiers[0]).to.be.an('object'); + expect(res.body.identifiers[0]).to.have.all.keys( + 'type', + 'value', + ); + expect(res.body.identifiers[0].type).to.be.a('string'); + expect(res.body.identifiers[0].value).to.be.a('string'); + }); + + it('GET {entity}/{BBID}/relationships should return relationships with a specific structure', async function () { + const res = await chai.request(app).get(`/author/${aBBID}/relationships`); + expect(res.body.relationships).to.be.an('array'); + expect(res.body.relationships[0]).to.have.all.keys( + 'direction', + 'id', + 'linkPhrase', + 'relationshipTypeId', + 'relationshipTypeName', + 'targetBbid', + 'targetEntityType' + ); + expect(res.body.relationships[0].direction).to.be.a('string'); + expect(res.body.relationships[0].id).to.be.a('number'); + expect(res.body.relationships[0].linkPhrase).to.be.a('string'); + expect(res.body.relationships[0].relationshipTypeId).to.be.a('number'); + expect(res.body.relationships[0].targetBbid).to.be.a('string'); + expect(res.body.relationships[0].targetEntityType).to.be.a('string'); + }); +}); diff --git a/test/src/api/routes/test-author.js b/test/src/api/routes/test-author.js new file mode 100644 index 0000000000..f75f3441f5 --- /dev/null +++ b/test/src/api/routes/test-author.js @@ -0,0 +1,158 @@ +/* eslint-disable prefer-arrow-callback,func-names */ +/* + * Copyright (C) 2019 Akhilesh Kumar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import {createAuthor, getRandomUUID, truncateEntities} from '../../../test-helpers/create-entities'; + +import app from '../../../../src/api/app'; +import chai from 'chai'; +import chaiHttp from 'chai-http'; + + +chai.use(chaiHttp); +const {expect} = chai; + + +const aBBID = getRandomUUID(); +const bBBID = getRandomUUID(); +const inValidBBID = 'akjd-adjjk-23123'; + +describe('GET /Author', () => { + before(() => createAuthor(aBBID)); + after(truncateEntities); + // Test to get basic information of an Author + it('should get basic information of an Author', async function () { + const res = await chai.request(app).get(`/author/${aBBID}`); + expect(res.status).to.equal(200); + expect(res.body).to.be.an('object'); + expect(res.body).to.have.all.keys( + 'bbid', + 'defaultAlias', + 'disambiguation', + 'type', + 'gender', + 'beginArea', + 'beginDate', + 'endArea', + 'endDate', + 'ended' + ); + }); + + it('should return list of aliases of an Author', async function () { + const res = await chai.request(app).get(`/author/${aBBID}/aliases`); + expect(res.status).to.equal(200); + expect(res.body).to.be.an('object'); + expect(res.body).to.have.all.keys( + 'bbid', + 'aliases' + ); + expect(res.body.aliases).to.be.an('array'); + expect(res.body.aliases).to.have.lengthOf(1); + }); + + it('should return list of identifiers of an Author', async function () { + const res = await chai.request(app).get(`/author/${aBBID}/identifiers`); + expect(res.status).to.equal(200); + expect(res.body).to.be.an('object'); + expect(res.body).to.have.all.keys( + 'bbid', + 'identifiers' + ); + expect(res.body.identifiers).to.be.an('array'); + expect(res.body.identifiers).to.have.lengthOf(1); + }); + + it('should return list of relationships of an Author', async function () { + const res = await chai.request(app).get(`/author/${aBBID}/relationships`); + expect(res.status).to.equal(200); + expect(res.body).to.be.an('object'); + expect(res.body).to.have.all.keys( + 'bbid', + 'relationships' + ); + expect(res.body.relationships).to.be.an('array'); + expect(res.body.relationships).to.have.lengthOf(1); + }); + + it('should throw a 404 error if trying to access an author that does not exist', function (done) { + chai.request(app) + .get(`/author/${bBBID}`) + .end(function (err, res) { + if (err) { return done(err); } + expect(res).to.have.status(404); + expect(res.ok).to.be.false; + expect(res.body).to.be.an('object'); + expect(res.body.message).to.equal('Author not found'); + return done(); + }); + }); + + it('should throw a 406 error if trying to access an author with invalid BBID', function (done) { + chai.request(app) + .get(`/author/${inValidBBID}`) + .end(function (err, res) { + if (err) { return done(err); } + expect(res).to.have.status(406); + expect(res.ok).to.be.false; + expect(res.body).to.be.an('object'); + expect(res.body.message).to.equal('BBID is not valid uuid'); + return done(); + }); + }); + + it('should throw a 404 error if trying to access identifiers of an Author that does not exist', function (done) { + chai.request(app) + .get(`/author/${bBBID}/identifiers`) + .end(function (err, res) { + if (err) { return done(err); } + expect(res).to.have.status(404); + expect(res.ok).to.be.false; + expect(res.body).to.be.an('object'); + expect(res.body.message).to.equal('Author not found'); + return done(); + }); + }); + + + it('should throw a 404 error if trying to access aliases of an Author that does not exist', function (done) { + chai.request(app) + .get(`/author/${bBBID}/aliases`) + .end(function (err, res) { + if (err) { return done(err); } + expect(res).to.have.status(404); + expect(res.ok).to.be.false; + expect(res.body).to.be.an('object'); + expect(res.body.message).to.equal('Author not found'); + return done(); + }); + }); + + it('should throw a 404 error if trying to access relationships of an Author that does not exist', function (done) { + chai.request(app) + .get(`/author/${bBBID}/relationships`) + .end(function (err, res) { + if (err) { return done(err); } + expect(res).to.have.status(404); + expect(res.ok).to.be.false; + expect(res.body).to.be.an('object'); + expect(res.body.message).to.equal('Author not found'); + return done(); + }); + }); +}); diff --git a/test/src/api/routes/test-edition-group.js b/test/src/api/routes/test-edition-group.js new file mode 100644 index 0000000000..6f8b9addce --- /dev/null +++ b/test/src/api/routes/test-edition-group.js @@ -0,0 +1,153 @@ +/* eslint-disable prefer-arrow-callback,func-names */ +/* + * Copyright (C) 2019 Akhilesh Kumar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import {createEditionGroup, getRandomUUID, truncateEntities} from '../../../test-helpers/create-entities'; + +import app from '../../../../src/api/app'; +import chai from 'chai'; +import chaiHttp from 'chai-http'; + + +chai.use(chaiHttp); +const {expect} = chai; + + +const aBBID = getRandomUUID(); +const bBBID = getRandomUUID(); +const inValidBBID = 'akjd-adjjk-23123'; + +describe('GET /EditionGroup', () => { + before(() => createEditionGroup(aBBID)); + after(truncateEntities); + // Test to get basic information of an Edition Group + it('should get basic information of an Edition Group', async function () { + const res = await chai.request(app).get(`/edition-group/${aBBID}`); + expect(res.status).to.equal(200); + expect(res.body).to.be.an('object'); + expect(res.body).to.have.all.keys( + 'bbid', + 'defaultAlias', + 'disambiguation', + 'type' + ); + }); + + it('should return list of aliases of an Edition Group', async function () { + const res = await chai.request(app).get(`/edition-group/${aBBID}/aliases`); + expect(res.status).to.equal(200); + expect(res.body).to.be.an('object'); + expect(res.body).to.have.all.keys( + 'bbid', + 'aliases' + ); + expect(res.body.aliases).to.be.an('array'); + expect(res.body.aliases).to.have.lengthOf(1); + }); + + it('should return list of identifiers of an Edition Group', async function () { + const res = await chai.request(app).get(`/edition-group/${aBBID}/identifiers`); + expect(res.status).to.equal(200); + expect(res.body).to.be.an('object'); + expect(res.body).to.have.all.keys( + 'bbid', + 'identifiers' + ); + expect(res.body.identifiers).to.be.an('array'); + expect(res.body.identifiers).to.have.lengthOf(1); + }); + + it('should return list of relationships of an Edition Group', async function () { + const res = await chai.request(app).get(`/edition-group/${aBBID}/relationships`); + expect(res.status).to.equal(200); + expect(res.body).to.be.an('object'); + expect(res.body).to.have.all.keys( + 'bbid', + 'relationships' + ); + expect(res.body.relationships).to.be.an('array'); + expect(res.body.relationships).to.have.lengthOf(1); + }); + + it('should throw a 404 error if trying to access an edition group that does not exist', function (done) { + chai.request(app) + .get(`/edition-group/${bBBID}`) + .end(function (err, res) { + if (err) { return done(err); } + expect(res).to.have.status(404); + expect(res.ok).to.be.false; + expect(res.body).to.be.an('object'); + expect(res.body.message).to.equal('Edition Group not found'); + return done(); + }); + }); + + it('should throw a 406 error if trying to access an edition with invalid BBID', function (done) { + chai.request(app) + .get(`/edition-group/${inValidBBID}`) + .end(function (err, res) { + if (err) { return done(err); } + expect(res).to.have.status(406); + expect(res.ok).to.be.false; + expect(res.body).to.be.an('object'); + expect(res.body.message).to.equal('BBID is not valid uuid'); + return done(); + }); + }); + + it('should throw a 404 error if trying to access identifiers of an Edition Group that does not exist', function (done) { + chai.request(app) + .get(`/edition-group/${bBBID}/identifiers`) + .end(function (err, res) { + if (err) { return done(err); } + expect(res).to.have.status(404); + expect(res.ok).to.be.false; + expect(res.body).to.be.an('object'); + expect(res.body.message).to.equal('Edition Group not found'); + return done(); + }); + }); + + + it('should throw a 404 error if trying to access aliases of an Edition Group that does not exist', function (done) { + chai.request(app) + .get(`/edition-group/${bBBID}/aliases`) + .end(function (err, res) { + if (err) { return done(err); } + expect(res).to.have.status(404); + expect(res.ok).to.be.false; + expect(res.body).to.be.an('object'); + expect(res.body.message).to.equal('Edition Group not found'); + return done(); + }); + }); + + it('should throw a 404 error if trying to access relationships of an Edition Group that does not exist', function (done) { + chai.request(app) + .get(`/edition-group/${bBBID}/relationships`) + .end(function (err, res) { + if (err) { return done(err); } + expect(res).to.have.status(404); + expect(res.ok).to.be.false; + expect(res.body).to.be.an('object'); + expect(res.body.message).to.equal('Edition Group not found'); + return done(); + }); + }); +}); + diff --git a/test/src/api/routes/test-edition.js b/test/src/api/routes/test-edition.js new file mode 100644 index 0000000000..c76ea8fa9d --- /dev/null +++ b/test/src/api/routes/test-edition.js @@ -0,0 +1,161 @@ +/* eslint-disable prefer-arrow-callback,func-names */ +/* + * Copyright (C) 2019 Akhilesh Kumar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import {createEdition, getRandomUUID, truncateEntities} from '../../../test-helpers/create-entities'; + +import app from '../../../../src/api/app'; +import chai from 'chai'; +import chaiHttp from 'chai-http'; + + +chai.use(chaiHttp); +const {expect} = chai; + + +const aBBID = getRandomUUID(); +const bBBID = getRandomUUID(); +const inValidBBID = 'akjd-adjjk-23123'; + +describe('GET /Edition', () => { + before(() => createEdition(aBBID)); + after(truncateEntities); + // Test to get basic information of an Edition + it('should get basic information of edition', async function () { + const res = await chai.request(app).get(`/edition/${aBBID}`); + expect(res.status).to.equal(200); + expect(res.body).to.be.an('object'); + expect(res.body).to.have.all.keys( + 'bbid', + 'defaultAlias', + 'languages', + 'disambiguation', + 'editionFormat', + 'height', + 'width', + 'depth', + 'pages', + 'status', + 'releaseEventDates', + 'weight' + ); + }); + + it('should return list of aliases of an Edition', async function () { + const res = await chai.request(app).get(`/edition/${aBBID}/aliases`); + expect(res.status).to.equal(200); + expect(res.body).to.be.an('object'); + expect(res.body).to.have.all.keys( + 'bbid', + 'aliases' + ); + expect(res.body.aliases).to.be.an('array'); + expect(res.body.aliases).to.have.lengthOf(1); + }); + + it('should return list of identifiers of an Edition', async function () { + const res = await chai.request(app).get(`/edition/${aBBID}/identifiers`); + expect(res.status).to.equal(200); + expect(res.body).to.be.an('object'); + expect(res.body).to.have.all.keys( + 'bbid', + 'identifiers' + ); + expect(res.body.identifiers).to.be.an('array'); + expect(res.body.identifiers).to.have.lengthOf(1); + }); + + it('should return list of relationships of an Edition', async function () { + const res = await chai.request(app).get(`/edition/${aBBID}/relationships`); + expect(res.status).to.equal(200); + expect(res.body).to.be.an('object'); + expect(res.body).to.have.all.keys( + 'bbid', + 'relationships' + ); + expect(res.body.relationships).to.be.an('array'); + expect(res.body.relationships).to.have.lengthOf(1); + }); + + it('should throw a 404 error if trying to access an edition that does not exist', function (done) { + chai.request(app) + .get(`/edition/${bBBID}`) + .end(function (err, res) { + if (err) { return done(err); } + expect(res).to.have.status(404); + expect(res.ok).to.be.false; + expect(res.body).to.be.an('object'); + expect(res.body.message).to.equal('Edition not found'); + return done(); + }); + }); + + it('should throw a 406 error if trying to access an edition with invalid BBID', function (done) { + chai.request(app) + .get(`/edition/${inValidBBID}`) + .end(function (err, res) { + if (err) { return done(err); } + expect(res).to.have.status(406); + expect(res.ok).to.be.false; + expect(res.body).to.be.an('object'); + expect(res.body.message).to.equal('BBID is not valid uuid'); + return done(); + }); + }); + + it('should throw a 404 error if trying to identifiers of an Edition that does not exist', function (done) { + chai.request(app) + .get(`/edition/${bBBID}/identifiers`) + .end(function (err, res) { + if (err) { return done(err); } + expect(res).to.have.status(404); + expect(res.ok).to.be.false; + expect(res.body).to.be.an('object'); + expect(res.body.message).to.equal('Edition not found'); + return done(); + }); + }); + + it('should throw a 404 error if trying to relationships of an Edition that does not exist', function (done) { + chai.request(app) + .get(`/edition/${bBBID}/relationships`) + .end(function (err, res) { + if (err) { return done(err); } + expect(res).to.have.status(404); + expect(res.ok).to.be.false; + expect(res.body).to.be.an('object'); + expect(res.body.message).to.equal('Edition not found'); + return done(); + }); + }); + + + it('should throw a 404 error if trying to access aliases of an Edition that does not exist', function (done) { + chai.request(app) + .get(`/edition/${bBBID}/aliases`) + .end(function (err, res) { + if (err) { return done(err); } + expect(res).to.have.status(404); + expect(res.ok).to.be.false; + expect(res.body).to.be.an('object'); + expect(res.body.message).to.equal('Edition not found'); + return done(); + }); + }); +}); + diff --git a/test/src/api/routes/test-publisher.js b/test/src/api/routes/test-publisher.js new file mode 100644 index 0000000000..272d1b3ac6 --- /dev/null +++ b/test/src/api/routes/test-publisher.js @@ -0,0 +1,155 @@ +/* eslint-disable prefer-arrow-callback,func-names */ +/* + * Copyright (C) 2019 Akhilesh Kumar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import {createPublisher, getRandomUUID, truncateEntities} from '../../../test-helpers/create-entities'; + +import app from '../../../../src/api/app'; +import chai from 'chai'; +import chaiHttp from 'chai-http'; + + +chai.use(chaiHttp); +const {expect} = chai; + + +const aBBID = getRandomUUID(); +const bBBID = getRandomUUID(); +const inValidBBID = 'akjd-adjjk-23123'; + +describe('GET /Publisher', () => { + before(() => createPublisher(aBBID)); + after(truncateEntities); + // Test to get basic information of a Publisher + it('should get basic information of a Publisher', async function () { + const res = await chai.request(app).get(`/publisher/${aBBID}`); + expect(res.status).to.equal(200); + expect(res.body).to.be.an('object'); + expect(res.body).to.have.all.keys( + 'bbid', + 'defaultAlias', + 'disambiguation', + 'type', + 'area', + 'beginDate', + 'endDate', + 'ended' + ); + }); + + it('should return list of aliases of a Publisher', async function () { + const res = await chai.request(app).get(`/publisher/${aBBID}/aliases`); + expect(res.status).to.equal(200); + expect(res.body).to.be.an('object'); + expect(res.body).to.have.all.keys( + 'bbid', + 'aliases' + ); + expect(res.body.aliases).to.be.an('array'); + expect(res.body.aliases).to.have.lengthOf(1); + }); + + it('should return list of identifiers of a Publisher', async function () { + const res = await chai.request(app).get(`/publisher/${aBBID}/identifiers`); + expect(res.status).to.equal(200); + expect(res.body).to.be.an('object'); + expect(res.body).to.have.all.keys( + 'bbid', + 'identifiers' + ); + expect(res.body.identifiers).to.be.an('array'); + expect(res.body.identifiers).to.have.lengthOf(1); + }); + it('should return list of relationships of a Publisher', async function () { + const res = await chai.request(app).get(`/publisher/${aBBID}/relationships`); + expect(res.status).to.equal(200); + expect(res.body).to.be.an('object'); + expect(res.body).to.have.all.keys( + 'bbid', + 'relationships' + ); + expect(res.body.relationships).to.be.an('array'); + expect(res.body.relationships).to.have.lengthOf(1); + }); + it('should throw a 404 error if trying to access a publisher that does not exist', function (done) { + chai.request(app) + .get(`/publisher/${bBBID}`) + .end(function (err, res) { + if (err) { return done(err); } + expect(res).to.have.status(404); + expect(res.ok).to.be.false; + expect(res.body).to.be.an('object'); + expect(res.body.message).to.equal('Publisher not found'); + return done(); + }); + }); + + it('should throw a 406 error if trying to access a publisher with invalid BBID', function (done) { + chai.request(app) + .get(`/publisher/${inValidBBID}`) + .end(function (err, res) { + if (err) { return done(err); } + expect(res).to.have.status(406); + expect(res.ok).to.be.false; + expect(res.body).to.be.an('object'); + expect(res.body.message).to.equal('BBID is not valid uuid'); + return done(); + }); + }); + + it('should throw a 404 error if trying to access identifiers of a Publisher that does not exist', function (done) { + chai.request(app) + .get(`/publisher/${bBBID}/identifiers`) + .end(function (err, res) { + if (err) { return done(err); } + expect(res).to.have.status(404); + expect(res.ok).to.be.false; + expect(res.body).to.be.an('object'); + expect(res.body.message).to.equal('Publisher not found'); + return done(); + }); + }); + + + it('should throw a 404 error if trying to access aliases of a Publisher that does not exist', function (done) { + chai.request(app) + .get(`/publisher/${bBBID}/aliases`) + .end(function (err, res) { + if (err) { return done(err); } + expect(res).to.have.status(404); + expect(res.ok).to.be.false; + expect(res.body).to.be.an('object'); + expect(res.body.message).to.equal('Publisher not found'); + return done(); + }); + }); + + it('should throw a 404 error if trying to access relationships of a Publisher that does not exist', function (done) { + chai.request(app) + .get(`/publisher/${bBBID}/relationships`) + .end(function (err, res) { + if (err) { return done(err); } + expect(res).to.have.status(404); + expect(res.ok).to.be.false; + expect(res.body).to.be.an('object'); + expect(res.body.message).to.equal('Publisher not found'); + return done(); + }); + }); +}); + diff --git a/test/src/api/routes/test-work.js b/test/src/api/routes/test-work.js new file mode 100644 index 0000000000..7b6fa4d9ba --- /dev/null +++ b/test/src/api/routes/test-work.js @@ -0,0 +1,155 @@ +/* eslint-disable prefer-arrow-callback,func-names */ +/* + * Copyright (C) 2019 Akhilesh Kumar + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +import {createWork, getRandomUUID, truncateEntities} from '../../../test-helpers/create-entities'; + +import app from '../../../../src/api/app'; +import chai from 'chai'; +import chaiHttp from 'chai-http'; + + +chai.use(chaiHttp); +const {expect} = chai; + + +const aBBID = getRandomUUID(); +const bBBID = getRandomUUID(); +const inValidBBID = 'akjd-adjjk-23123'; + +describe('GET /work', () => { + before(() => createWork(aBBID)); + after(truncateEntities); + // Test to get basic information of a Work + it('should get basic information of work', async function () { + const res = await chai.request(app).get(`/work/${aBBID}`); + expect(res.status).to.equal(200); + expect(res.body).to.be.an('object'); + expect(res.body).to.have.all.keys( + 'bbid', + 'defaultAlias', + 'languages', + 'disambiguation', + 'workType', + 'entityType' + ); + }); + + it('should return list of aliases of a Work', async function () { + const res = await chai.request(app).get(`/work/${aBBID}/aliases`); + expect(res.status).to.equal(200); + expect(res.body).to.be.an('object'); + expect(res.body).to.have.all.keys( + 'bbid', + 'aliases' + ); + expect(res.body.aliases).to.be.an('array'); + expect(res.body.aliases).to.have.lengthOf(1); + }); + + it('should return list of identifiers of work', async function () { + const res = await chai.request(app).get(`/work/${aBBID}/identifiers`); + expect(res.status).to.equal(200); + expect(res.body).to.be.an('object'); + expect(res.body).to.have.all.keys( + 'bbid', + 'identifiers' + ); + expect(res.body.identifiers).to.be.an('array'); + expect(res.body.identifiers).to.have.lengthOf(1); + }); + + it('should return list of relationships of a Work', async function () { + const res = await chai.request(app).get(`/work/${aBBID}/relationships`); + expect(res.status).to.equal(200); + expect(res.body).to.be.an('object'); + expect(res.body).to.have.all.keys( + 'bbid', + 'relationships' + ); + expect(res.body.relationships).to.be.an('array'); + expect(res.body.relationships).to.have.lengthOf(1); + }); + + it('should throw a 404 error if trying to access a work that does not exist', function (done) { + chai.request(app) + .get(`/work/${bBBID}`) + .end(function (err, res) { + if (err) { return done(err); } + expect(res).to.have.status(404); + expect(res.ok).to.be.false; + expect(res.body).to.be.an('object'); + expect(res.body.message).to.equal('Work not found'); + return done(); + }); + }); + + it('should throw a 406 error if trying to access a work with invalid BBID', function (done) { + chai.request(app) + .get(`/work/${inValidBBID}`) + .end(function (err, res) { + if (err) { return done(err); } + expect(res).to.have.status(406); + expect(res.ok).to.be.false; + expect(res.body).to.be.an('object'); + expect(res.body.message).to.equal('BBID is not valid uuid'); + return done(); + }); + }); + + it('should throw a 404 error if trying to access identifiers of a Work that does not exist', function (done) { + chai.request(app) + .get(`/work/${bBBID}/identifiers`) + .end(function (err, res) { + if (err) { return done(err); } + expect(res).to.have.status(404); + expect(res.ok).to.be.false; + expect(res.body).to.be.an('object'); + expect(res.body.message).to.equal('Work not found'); + return done(); + }); + }); + + + it('should throw a 404 error if trying to access aliases of a Work that does not exist', function (done) { + chai.request(app) + .get(`/work/${bBBID}/aliases`) + .end(function (err, res) { + if (err) { return done(err); } + expect(res).to.have.status(404); + expect(res.ok).to.be.false; + expect(res.body).to.be.an('object'); + expect(res.body.message).to.equal('Work not found'); + return done(); + }); + }); + + it('should throw a 404 error if trying to access relationships of a Work that does not exist', function (done) { + chai.request(app) + .get(`/work/${bBBID}/relationships`) + .end(function (err, res) { + if (err) { return done(err); } + expect(res).to.have.status(404); + expect(res.ok).to.be.false; + expect(res.body).to.be.an('object'); + expect(res.body.message).to.equal('Work not found'); + return done(); + }); + }); +}); + diff --git a/test/test-helpers/create-entities.js b/test/test-helpers/create-entities.js index badf4f4fa6..7fbd414764 100644 --- a/test/test-helpers/create-entities.js +++ b/test/test-helpers/create-entities.js @@ -22,11 +22,14 @@ import uuidv4 from 'uuid/v4'; const { - bookshelf, util, Editor, EditorType, Revision, RelationshipSet, - Alias, AliasSet, Identifier, IdentifierType, IdentifierSet, + bookshelf, util, Editor, EditorType, Revision, Relationship, RelationshipType, RelationshipSet, + Alias, AliasSet, Area, Identifier, IdentifierType, IdentifierSet, Disambiguation, Entity, Annotation, Gender, - Author, Edition, EditionGroup, Publisher, Work + Author, Edition, EditionGroup, Publisher, Work, + Language, WorkType, EditionGroupType, AuthorType, PublisherType } = orm; +const {updateLanguageSet} = orm.func.language; + const setData = {id: 1}; @@ -45,8 +48,19 @@ export const editorAttribs = { typeId: 1 }; +const languageAttribs = { + frequency: 1, + id: 1, + isoCode1: 'en', + isoCode2b: 'eng', + isoCode2t: 'eng', + isoCode3: 'eng', + name: 'English' +}; + const aliasData = { ...setData, + languageId: 42, name: 'work name', sortName: 'Work sort name' }; @@ -71,6 +85,16 @@ const identifierTypeData = { validationRegex: 'test' }; +const relationshipTypeData = { + description: 'test descryption', + id: 1, + label: 'test label', + linkPhrase: 'test phrase', + reverseLinkPhrase: 'test reverse link phrase', + sourceEntityType: 'Author', + targetEntityType: 'Work' +}; + const entityAttribs = { aliasSetId: 1, annotationId: 1, @@ -92,34 +116,101 @@ export async function createEditor() { } async function createAliasAndAliasSet() { - await new Alias(aliasData) + await new Language({...languageAttribs, id: aliasData.languageId}) .save(null, {method: 'insert'}); - await new AliasSet({...setData, defaultAliasId: 1}) + const alias = await new Alias(aliasData) .save(null, {method: 'insert'}); + await new AliasSet({ + ...setData, + defaultAliasId: alias.get('id') + }) + .save(null, {method: 'insert'}) + .then((model) => model.aliases().attach([alias])); } + async function createIdentifierAndIdentifierSet() { await new IdentifierType(identifierTypeData) .save(null, {method: 'insert'}); + const identifier = await new Identifier(identifierData) + .save(null, {method: 'insert'}); await new IdentifierSet(setData) + .save(null, {method: 'insert'}) + .then((model) => model.identifiers().attach([identifier])); +} + +async function createRelationshipSet(sourceBbid, targetBbid, entityType, targetEntityType) { + const safeTargetBbid = targetBbid || uuidv4(); + const safeSourceBbid = sourceBbid || uuidv4(); + const relationshipData = { + id: 1, + sourceBbid: safeSourceBbid, + targetBbid: safeTargetBbid, + typeId: 1 + }; + + if (!sourceBbid) { + // We're only creating a relationship set for show, + // we don't care what type of entity we use + await new Entity({bbid: safeSourceBbid, type: entityType || 'Author'}) + .save(null, {method: 'insert'}); + } + await new RelationshipType(relationshipTypeData) + .save(null, {method: 'insert'}); + await new Entity({bbid: safeTargetBbid, type: targetEntityType || 'Author'}) + .save(null, {method: 'insert'}); + + const relationship = await new Relationship(relationshipData) + .save(null, {method: 'insert'}); + await new RelationshipSet(setData) + .save(null, {method: 'insert'}) + .then( + (model) => + model.relationships().attach([relationship]).then(() => model) + ); +} + +async function createRelationshipAndRelationshipSet(sourceBbid, targetBbid, targetEntityType) { + const relationshipData = { + id: 1, + sourceBbid, + targetBbid, + typeId: 1 + }; + await new Entity({bbid: targetBbid, type: targetEntityType}) + .save(null, {method: 'insert'}); + await new RelationshipType(relationshipTypeData) .save(null, {method: 'insert'}); - await new Identifier(identifierData) + const relationship = await new Relationship(relationshipData) .save(null, {method: 'insert'}); + await new RelationshipSet({id: 42}) + .save(null, {method: 'insert'}) + .then((model) => model.relationships().attach([relationship])); } -async function createRelationshipSet() { + +async function createLanguageSet() { // Create relationships here if you need them - await new RelationshipSet(setData) + await new Language(languageAttribs) + .save(null, {method: 'insert'}); + await new Language({...languageAttribs, id: 2}) .save(null, {method: 'insert'}); + const languageSet = await updateLanguageSet( + orm, + null, + null, + [{id: 1}, {id: 2}] + ); + return languageSet.get('id'); } export function getRandomUUID() { return uuidv4(); } -async function createEntityPrerequisites() { +async function createEntityPrerequisites(entityBbid) { await createEditor(); await createAliasAndAliasSet(); await createIdentifierAndIdentifierSet(); - await createRelationshipSet(); + await createRelationshipSet(entityBbid); await new Disambiguation({ ...setData, @@ -138,7 +229,6 @@ async function createEntityPrerequisites() { export async function createEdition(optionalBBID) { const bbid = optionalBBID || uuidv4(); - await createEntityPrerequisites(); await new Entity({bbid, type: 'Edition'}) @@ -149,12 +239,87 @@ export async function createEdition(optionalBBID) { export async function createWork(optionalBBID) { const bbid = optionalBBID || uuidv4(); + await new Entity({bbid, type: 'Work'}) + .save(null, {method: 'insert'}); + await createEntityPrerequisites(bbid); + // await createRelationshipAndRelationshipSet(bbid, uuidv4(), 'Author'); + const languageSetId = await createLanguageSet(); + + const workAttribs = { + bbid, + languageSetId, + typeId: setData.id + }; + await new WorkType({...setData, label: 'Work Type 1'}) + .save(null, {method: 'insert'}); + await new Work({...entityAttribs, ...workAttribs}) + .save(null, {method: 'insert'}); +} +export async function createEditionGroup(optionalBBID) { + const bbid = optionalBBID || uuidv4(); await createEntityPrerequisites(); + const editionGroupAttribs = { + bbid, + typeId: setData.id + }; + await new EditionGroupType({...setData, label: 'Edition Group Type 1'}) + .save(null, {method: 'insert'}); + await new Entity({bbid, type: 'EditionGroup'}) + .save(null, {method: 'insert'}); + await new EditionGroup({...entityAttribs, ...editionGroupAttribs}) + .save(null, {method: 'insert'}); +} - await new Entity({bbid, type: 'Work'}) +export async function createAuthor(optionalBBID) { + const bbid = optionalBBID || uuidv4(); + await createEntityPrerequisites(); + const authorAttribs = { + bbid, + beginAreaId: setData.id, + beginDay: 25, + beginMonth: 12, + beginYear: 2000, + endAreaId: setData.id, + endDay: 10, + endMonth: 5, + endYear: 2012, + ended: true, + genderId: setData.id, + typeId: setData.id + }; + await new Area({...setData, gid: uuidv4(), name: 'Rlyeh'}) + .save(null, {method: 'insert'}); + await new AuthorType({...setData, label: 'Author Type 1'}) + .save(null, {method: 'insert'}); + await new Entity({bbid, type: 'Author'}) + .save(null, {method: 'insert'}); + await new Author({...entityAttribs, ...authorAttribs}) + .save(null, {method: 'insert'}); +} + +export async function createPublisher(optionalBBID) { + const bbid = optionalBBID || uuidv4(); + await createEntityPrerequisites(); + const publisherAttribs = { + areaId: setData.id, + bbid, + beginDay: 25, + beginMonth: 12, + beginYear: 2000, + endDay: 10, + endMonth: 5, + endYear: 2012, + ended: true, + typeId: setData.id + }; + await new Area({...setData, gid: uuidv4(), name: 'Rlyeh'}) + .save(null, {method: 'insert'}); + await new PublisherType({...setData, label: 'Publisher Type 1'}) + .save(null, {method: 'insert'}); + await new Entity({bbid, type: 'Publisher'}) .save(null, {method: 'insert'}); - await new Work({...entityAttribs, bbid}) + await new Publisher({...entityAttribs, ...publisherAttribs}) .save(null, {method: 'insert'}); } @@ -168,11 +333,18 @@ export function truncateEntities() { 'bookbrainz.identifier_set', 'bookbrainz.identifier_type', 'bookbrainz.relationship', + 'bookbrainz.relationship_type', 'bookbrainz.relationship_set', 'bookbrainz.disambiguation', 'bookbrainz.entity', 'bookbrainz.revision', 'bookbrainz.annotation', + 'bookbrainz.work_type', + 'bookbrainz.edition_group_type', + 'bookbrainz.author_type', + 'bookbrainz.publisher_type', + 'musicbrainz.area', + 'musicbrainz.language', 'musicbrainz.gender' ]); }