From e8cc92ff9693a4cf6ecb2bdf07e0a915c92111a7 Mon Sep 17 00:00:00 2001 From: CaramelKat <32065563+caramelkat@users.noreply.github.com> Date: Fri, 20 Nov 2020 12:11:39 -0600 Subject: [PATCH] initial commit --- package-lock.json | 906 +++ package.json | 20 + src/config.example.json | 14 + src/database.js | 175 + src/logger.js | 52 + src/middleware/client-header.js | 38 + src/middleware/pnid.js | 50 + src/middleware/session.js | 23 + src/middleware/xml-parser.js | 35 + src/models/communities.js | 64 + src/models/endpoint.js | 27 + src/models/post.js | 103 + src/models/topic.js | 79 + src/models/user.js | 152 + src/server.js | 64 + src/services/juxt-web/index.js | 27 + .../juxt-web/routes/ctr/communities.js | 62 + src/services/juxt-web/routes/ctr/show.js | 35 + src/services/juxt-web/routes/ctr/web.js | 22 + src/services/juxt-web/routes/index.js | 8 + .../juxt-web/routes/portal/communities.js | 89 + src/services/juxt-web/routes/portal/show.js | 35 + src/services/juxt-web/routes/portal/web.js | 22 + src/views/ctr_communities.ejs | 74 + src/views/ctr_show.ejs | 16 + src/views/index.ejs | 12 + src/views/portal_communities.ejs | 71 + src/views/portal_communities_ajax.ejs | 52 + src/views/portal_community.ejs | 133 + src/views/portal_community_ajax.ejs | 114 + src/views/portal_community_boxy_ajax.ejs | 85 + src/views/portal_show.ejs | 72 + src/webfiles/ctr/css/juxt.css | 468 ++ .../ctr/icons/Icon-feather-search.svg | 15 + src/webfiles/ctr/icons/Path 53.svg | 14 + src/webfiles/ctr/icons/mario-kart.jpg | Bin 0 -> 128196 bytes src/webfiles/ctr/icons/mario-kart.png | Bin 0 -> 779293 bytes src/webfiles/ctr/js/juxt.js | 228 + src/webfiles/portal/css/juxt.css | 1105 ++++ src/webfiles/portal/fonts/Poppins-Light.eot | Bin 0 -> 160024 bytes src/webfiles/portal/fonts/Poppins-Light.otf | Bin 0 -> 468168 bytes src/webfiles/portal/fonts/Poppins-Light.svg | 4870 +++++++++++++++++ src/webfiles/portal/fonts/Poppins-Light.ttf | Bin 0 -> 159848 bytes src/webfiles/portal/fonts/Poppins-Light.woff | Bin 0 -> 163292 bytes .../portal/icons/Icon-feather-search.svg | 15 + src/webfiles/portal/icons/Path 53.svg | 14 + src/webfiles/portal/icons/mario-kart.jpg | Bin 0 -> 128196 bytes src/webfiles/portal/js/juxt.js | 506 ++ 48 files changed, 9966 insertions(+) create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/config.example.json create mode 100644 src/database.js create mode 100644 src/logger.js create mode 100644 src/middleware/client-header.js create mode 100644 src/middleware/pnid.js create mode 100644 src/middleware/session.js create mode 100644 src/middleware/xml-parser.js create mode 100644 src/models/communities.js create mode 100644 src/models/endpoint.js create mode 100644 src/models/post.js create mode 100644 src/models/topic.js create mode 100644 src/models/user.js create mode 100644 src/server.js create mode 100644 src/services/juxt-web/index.js create mode 100644 src/services/juxt-web/routes/ctr/communities.js create mode 100644 src/services/juxt-web/routes/ctr/show.js create mode 100644 src/services/juxt-web/routes/ctr/web.js create mode 100644 src/services/juxt-web/routes/index.js create mode 100644 src/services/juxt-web/routes/portal/communities.js create mode 100644 src/services/juxt-web/routes/portal/show.js create mode 100644 src/services/juxt-web/routes/portal/web.js create mode 100644 src/views/ctr_communities.ejs create mode 100644 src/views/ctr_show.ejs create mode 100644 src/views/index.ejs create mode 100644 src/views/portal_communities.ejs create mode 100644 src/views/portal_communities_ajax.ejs create mode 100644 src/views/portal_community.ejs create mode 100644 src/views/portal_community_ajax.ejs create mode 100644 src/views/portal_community_boxy_ajax.ejs create mode 100644 src/views/portal_show.ejs create mode 100644 src/webfiles/ctr/css/juxt.css create mode 100644 src/webfiles/ctr/icons/Icon-feather-search.svg create mode 100644 src/webfiles/ctr/icons/Path 53.svg create mode 100644 src/webfiles/ctr/icons/mario-kart.jpg create mode 100644 src/webfiles/ctr/icons/mario-kart.png create mode 100644 src/webfiles/ctr/js/juxt.js create mode 100644 src/webfiles/portal/css/juxt.css create mode 100644 src/webfiles/portal/fonts/Poppins-Light.eot create mode 100644 src/webfiles/portal/fonts/Poppins-Light.otf create mode 100644 src/webfiles/portal/fonts/Poppins-Light.svg create mode 100644 src/webfiles/portal/fonts/Poppins-Light.ttf create mode 100644 src/webfiles/portal/fonts/Poppins-Light.woff create mode 100644 src/webfiles/portal/icons/Icon-feather-search.svg create mode 100644 src/webfiles/portal/icons/Path 53.svg create mode 100644 src/webfiles/portal/icons/mario-kart.jpg create mode 100644 src/webfiles/portal/js/juxt.js diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..33c9397 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,906 @@ +{ + "name": "juxt-web", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@oozcitak/dom": { + "version": "1.15.8", + "resolved": "https://registry.npmjs.org/@oozcitak/dom/-/dom-1.15.8.tgz", + "integrity": "sha512-MoOnLBNsF+ok0HjpAvxYxR4piUhRDCEWK0ot3upwOOHYudJd30j6M+LNcE8RKpwfnclAX9T66nXXzkytd29XSw==", + "requires": { + "@oozcitak/infra": "1.0.8", + "@oozcitak/url": "1.0.4", + "@oozcitak/util": "8.3.8" + } + }, + "@oozcitak/infra": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@oozcitak/infra/-/infra-1.0.8.tgz", + "integrity": "sha512-JRAUc9VR6IGHOL7OGF+yrvs0LO8SlqGnPAMqyzOuFZPSZSXI7Xf2O9+awQPSMXgIWGtgUf/dA6Hs6X6ySEaWTg==", + "requires": { + "@oozcitak/util": "8.3.8" + } + }, + "@oozcitak/url": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@oozcitak/url/-/url-1.0.4.tgz", + "integrity": "sha512-kDcD8y+y3FCSOvnBI6HJgl00viO/nGbQoCINmQ0h98OhnGITrWR3bOGfwYCthgcrV8AnTJz8MzslTQbC3SOAmw==", + "requires": { + "@oozcitak/infra": "1.0.8", + "@oozcitak/util": "8.3.8" + } + }, + "@oozcitak/util": { + "version": "8.3.8", + "resolved": "https://registry.npmjs.org/@oozcitak/util/-/util-8.3.8.tgz", + "integrity": "sha512-T8TbSnGsxo6TDBJx/Sgv/BlVJL3tshxZP7Aq5R1mSnM5OcHY2dQaxLMu2+E8u3gN0MLOzdjurqN4ZRVuzQycOQ==" + }, + "@types/node": { + "version": "14.6.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.6.2.tgz", + "integrity": "sha512-onlIwbaeqvZyniGPfdw/TEhKIh79pz66L1q06WUQqJLnAb6wbjvOtepLYTGHTqzdXgBYIE3ZdmqHDGsRsbBz7A==" + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "async": { + "version": "0.9.2", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", + "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", + "dev": true + }, + "at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "bl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", + "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", + "requires": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "bluebird": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", + "integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==" + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + } + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "bson": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.5.tgz", + "integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg==" + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "dank-each": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dank-each/-/dank-each-1.0.0.tgz", + "integrity": "sha1-qGE0Okso02IgPUBcd4ZNyaZANzA=", + "dev": true + }, + "dank-map": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/dank-map/-/dank-map-0.1.0.tgz", + "integrity": "sha1-6Z5384LGjy5asrPzqCuAMZV1Kag=", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "denque": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", + "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==" + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "ejs": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.5.tgz", + "integrity": "sha512-dldq3ZfFtgVTJMLjOe+/3sROTzALlL9E34V4/sDtUd/KlBSS0s6U1/+WPE1B4sj9CXHJpL1M6rhNJnc9Wbal9w==", + "dev": true, + "requires": { + "jake": "^10.6.1" + } + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "express-subdomain": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/express-subdomain/-/express-subdomain-1.0.5.tgz", + "integrity": "sha1-mQ75eUC39MKCPZWTZIt5voWKY4s=", + "dev": true + }, + "filelist": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.1.tgz", + "integrity": "sha512-8zSK6Nu0DQIC08mUC46sWGXi+q3GGpKydAG36k+JDba6VRpkevvOWUW5a/PhShij4+vHT9M+ghgG7eM+a9JDUQ==", + "dev": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "fs-extra": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.0.1.tgz", + "integrity": "sha512-h2iAoN838FqAFJY2/qVpzFXy+EBxfVE220PalAqQLDVsFOHLJrZvut5puAbCdNv6WJk+B8ihI+k0c7JK5erwqQ==", + "requires": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^1.0.0" + } + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "jake": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.8.2.tgz", + "integrity": "sha512-eLpKyrfG3mzvGE2Du8VoPbeSkRry093+tyNjdYaBbJS9v17knImYGNXQCUV0gLxQtF82m3E8iRb/wdSQZLoq7A==", + "dev": true, + "requires": { + "async": "0.9.x", + "chalk": "^2.4.2", + "filelist": "^1.0.1", + "minimatch": "^3.0.4" + } + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + }, + "dependencies": { + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==" + } + } + }, + "kareem": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.3.1.tgz", + "integrity": "sha512-l3hLhffs9zqoDe8zjmb/mAN4B8VT3L56EUvKNqLFVs9YlFA+zx7ke1DO8STAdDyYNkeSo1nKmjuvQeI12So8Xw==" + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "memory-cache": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/memory-cache/-/memory-cache-0.2.0.tgz", + "integrity": "sha1-eJCwHVLADI68nVM+H46xfjA0hxo=" + }, + "memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "optional": true + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "requires": { + "mime-db": "1.44.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==" + }, + "mongodb": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.3.tgz", + "integrity": "sha512-rOZuR0QkodZiM+UbQE5kDsJykBqWi0CL4Ec2i1nrGrUI3KO11r6Fbxskqmq3JK2NH7aW4dcccBuUujAP0ERl5w==", + "requires": { + "bl": "^2.2.1", + "bson": "^1.1.4", + "denque": "^1.4.1", + "require_optional": "^1.0.1", + "safe-buffer": "^5.1.2", + "saslprep": "^1.0.0" + } + }, + "mongoose": { + "version": "5.10.14", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-5.10.14.tgz", + "integrity": "sha512-lG7TCyjGjVPM4R1HWCyWZbdK7hC4MdWRG7P95hDENXSvaLkGLiP7WZbGT83KzLY8+400x1p6C53UALD3MqpsCA==", + "requires": { + "bson": "^1.1.4", + "kareem": "2.3.1", + "mongodb": "3.6.3", + "mongoose-legacy-pluralize": "1.0.2", + "mpath": "0.7.0", + "mquery": "3.2.2", + "ms": "2.1.2", + "regexp-clone": "1.0.0", + "safe-buffer": "5.2.1", + "sift": "7.0.1", + "sliced": "1.0.1" + }, + "dependencies": { + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, + "mongoose-legacy-pluralize": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz", + "integrity": "sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==" + }, + "morgan": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", + "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", + "requires": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.0.2" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + } + } + }, + "mpath": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.7.0.tgz", + "integrity": "sha512-Aiq04hILxhz1L+f7sjGyn7IxYzWm1zLNNXcfhDtx04kZ2Gk7uvFdgZ8ts1cWa/6d0TQmag2yR8zSGZUmp0tFNg==" + }, + "mquery": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-3.2.2.tgz", + "integrity": "sha512-XB52992COp0KP230I3qloVUbkLUxJIu328HBP2t2EsxSFtf4W1HPSOBWOXf1bqxK4Xbb66lfMJ+Bpfd9/yZE1Q==", + "requires": { + "bluebird": "3.5.1", + "debug": "3.1.0", + "regexp-clone": "^1.0.0", + "safe-buffer": "5.1.2", + "sliced": "1.0.1" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + } + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "object-to-xml": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/object-to-xml/-/object-to-xml-2.0.0.tgz", + "integrity": "sha512-bArXy7WCF1V9R88/zF9adSZSeFQnFmmKhMqNuNLAxqrbkvzcWP8HgnaRCcVJsfvIgvpdHiYd0qzJi7LM7QFfcQ==", + "dev": true, + "requires": { + "dank-each": "^1.0.0", + "dank-map": "~0.1.0", + "sanitizer": "0.1.3" + } + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==" + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "proxy-addr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.1" + } + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "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" + } + }, + "regexp-clone": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-1.0.0.tgz", + "integrity": "sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw==" + }, + "require_optional": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", + "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", + "requires": { + "resolve-from": "^2.0.0", + "semver": "^5.1.0" + } + }, + "resolve-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", + "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sanitizer": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/sanitizer/-/sanitizer-0.1.3.tgz", + "integrity": "sha1-1PCvdHXZp7ryqeWmEXGLqheKOeE=", + "dev": true + }, + "saslprep": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", + "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", + "optional": true, + "requires": { + "sparse-bitfield": "^3.0.3" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "sift": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/sift/-/sift-7.0.1.tgz", + "integrity": "sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g==" + }, + "sliced": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", + "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" + }, + "sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", + "optional": true, + "requires": { + "memory-pager": "^1.0.2" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "universalify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-1.0.0.tgz", + "integrity": "sha512-rb6X1W158d7pRQBg5gkR8uPaSfiids68LTJQYOtEUhoJUWBdaQHsuT/EUduxXYxcrt4r5PJ4fuHW1MHT6p0qug==" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "xmlbuilder": { + "version": "15.1.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz", + "integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==" + }, + "xmlbuilder2": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/xmlbuilder2/-/xmlbuilder2-2.4.0.tgz", + "integrity": "sha512-KrOVUGD65xTQ7ZA+GMQGdBSpe1Ufu5ylCQSYVk6QostySDkxPmAQ0WWIu7dR3JjLfVbF22RFQX7KyrZ6VTLcQg==", + "requires": { + "@oozcitak/dom": "1.15.8", + "@oozcitak/infra": "1.0.8", + "@oozcitak/util": "8.3.8", + "@types/node": "14.6.2", + "js-yaml": "3.14.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..bce3fde --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "juxt-web", + "version": "1.0.0", + "dependencies": { + "colors": "^1.4.0", + "express": "^4.17.1", + "fs-extra": "^9.0.1", + "memory-cache": "^0.2.0", + "moment": "^2.29.1", + "mongoose": "^5.10.14", + "morgan": "^1.10.0", + "xmlbuilder": "^15.1.1", + "xmlbuilder2": "^2.4.0" + }, + "devDependencies": { + "ejs": "^3.1.5", + "express-subdomain": "^1.0.5", + "object-to-xml": "^2.0.0" + } +} diff --git a/src/config.example.json b/src/config.example.json new file mode 100644 index 0000000..f985802 --- /dev/null +++ b/src/config.example.json @@ -0,0 +1,14 @@ +{ + "http": { + "port": 80 + }, + "secret": "[Database Secret Key]", + "mongoose": { + "uri": "mongodb://localhost:27017", + "database": "[Database Name]", + "options": { + "useNewUrlParser": true, + "useUnifiedTopology": true + } + } +} \ No newline at end of file diff --git a/src/database.js b/src/database.js new file mode 100644 index 0000000..812825f --- /dev/null +++ b/src/database.js @@ -0,0 +1,175 @@ +const mongoose = require('mongoose'); +const { mongoose: mongooseConfig } = require('./config.json'); +const { TOPIC } = require('./models/topic'); +const { ENDPOINT } = require('./models/endpoint'); +const { COMMUNITY } = require('./models/communities'); +const { POST } = require('./models/post'); +const { USER } = require('./models/user'); +const { uri, database, options } = mongooseConfig; + +let connection; + +async function connect() { + await mongoose.connect(`${uri}/${database}`, options); + + connection = mongoose.connection; + connection.on('error', console.error.bind(console, 'connection error:')); +} + +function verifyConnected() { + if (!connection) { + throw new Error('Cannot make database requets without being connected'); + } +} + +async function getTopicByName(topicName) { + verifyConnected(); + + if (typeof topicName !== 'string') { + return null; + } + + return TOPIC.findOne({ + name: topicName + }); +} + +async function getTopicByCommunityID(communityID) { + verifyConnected(); + + if (typeof communityID !== 'string') { + return null; + } + + return TOPIC.findOne({ + community_id: communityID + }); +} + +async function getCommunities(numberOfCommunities) { + verifyConnected(); + return COMMUNITY.find({}).limit(numberOfCommunities); +} + +async function getMostPopularCommunities(numberOfCommunities) { + verifyConnected(); + return COMMUNITY.find({}).sort({followers: -1}).limit(numberOfCommunities); +} + +async function getNewCommunities(numberOfCommunities) { + verifyConnected(); + return COMMUNITY.find({}).sort([['created_at', -1]]).limit(numberOfCommunities); +} + +async function getCommunityByTitleID(title_id) { + verifyConnected(); + return COMMUNITY.findOne({ + title_id: title_id + }); +} + +async function getCommunityByID(community_id) { + verifyConnected(); + return COMMUNITY.findOne({ + id: community_id + }); +} + +async function getNumberPostsByCommunity(community) { + verifyConnected(); + return POST.find({ + title_id: community.title_id + }).countDocuments(); +} + +async function getPostByID(postID) { + verifyConnected(); + + return POST.findOne({ + id: postID + }); +} + +async function getPostsByUserID(userID) { + verifyConnected(); + + return POST.find({ + pid: userID + }); +} + +async function getNumberPostsByUserID(userID) { + verifyConnected(); + return POST.find({ + pid: userID + }).countDocuments(); +} + +async function getHotPostsByCommunity(community, numberOfPosts) { + verifyConnected(); + return POST.find({ + title_id: community.title_id + }).sort({empathy_count: -1}).limit(numberOfPosts); +} +async function getPostsByCommunity(community, numberOfPosts) { + verifyConnected(); + return POST.find({ + title_id: community.title_id + }).limit(numberOfPosts); +} + +async function getPostsByCommunityKey(community, numberOfPosts, search_key) { + verifyConnected(); + return POST.find({ + title_id: community.title_id, + search_key: search_key + }).limit(numberOfPosts); +} + +async function getNewPostsByCommunity(community, numberOfPosts) { + verifyConnected(); + return POST.find({ + title_id: community.title_id + }).sort({ created_at: -1 }).limit(numberOfPosts); +} + +async function getDiscoveryHosts() { + verifyConnected(); + return ENDPOINT.findOne({ + version: 1 + }); +} + +async function getUserByPID(PID) { + verifyConnected(); + + return USER.findOne({ + pid: PID + }); +} + +async function getServerConfig() { + verifyConnected(); + return ENDPOINT.findOne({ + type: "config" + }); +} + +module.exports = { + connect, + getCommunities, + getMostPopularCommunities, + getNewCommunities, + getCommunityByTitleID, + getCommunityByID, + getNumberPostsByCommunity, + getDiscoveryHosts, + getPostsByCommunity, + getHotPostsByCommunity, + getNewPostsByCommunity, + getPostsByCommunityKey, + getPostsByUserID, + getNumberPostsByUserID, + getPostByID, + getUserByPID, +}; \ No newline at end of file diff --git a/src/logger.js b/src/logger.js new file mode 100644 index 0000000..eda058e --- /dev/null +++ b/src/logger.js @@ -0,0 +1,52 @@ +const fs = require('fs-extra'); +require('colors'); + +const root = __dirname; +fs.ensureDirSync(`${root}/logs`); + +const streams = { + latest: fs.createWriteStream(`${root}/logs/latest.log`), + success: fs.createWriteStream(`${root}/logs/success.log`), + error: fs.createWriteStream(`${root}/logs/error.log`), + warn: fs.createWriteStream(`${root}/logs/warn.log`), + info: fs.createWriteStream(`${root}/logs/info.log`) +}; + +function success(input) { + const time = new Date(); + input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [SUCCESS]: ${input}`; + streams.success.write(`${input}\n`); + + console.log(`${input}`.green.bold); +} + +function error(input) { + const time = new Date(); + input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [ERROR]: ${input}`; + streams.error.write(`${input}\n`); + + console.log(`${input}`.red.bold); +} + +function warn(input) { + const time = new Date(); + input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [WARN]: ${input}`; + streams.warn.write(`${input}\n`); + + console.log(`${input}`.yellow.bold); +} + +function info(input) { + const time = new Date(); + input = `[${time.getHours()}:${time.getMinutes()}:${time.getSeconds()}] [INFO]: ${input}`; + streams.info.write(`${input}\n`); + + console.log(`${input}`.cyan.bold); +} + +module.exports = { + success, + error, + warn, + info +}; \ No newline at end of file diff --git a/src/middleware/client-header.js b/src/middleware/client-header.js new file mode 100644 index 0000000..e722968 --- /dev/null +++ b/src/middleware/client-header.js @@ -0,0 +1,38 @@ +const xmlbuilder = require('xmlbuilder'); + +const VALID_CLIENT_ID_SECRET_PAIRS = { + // 'Key' is the client ID, 'Value' is the client secret + 'a2efa818a34fa16b8afbc8a74eba3eda': 'c91cdb5658bd4954ade78533a339cf9a', // Possibly WiiU exclusive? + 'daf6227853bcbdce3d75baee8332b': '3eff548eac636e2bf45bb7b375e7b6b0', // Possibly 3DS exclusive? + 'ea25c66c26b403376b4c5ed94ab9cdea': 'd137be62cb6a2b831cad8c013b92fb55', // Possibly 3DS exclusive? +}; + + +function nintendoClientHeaderCheck(request, response, next) { + response.set('Content-Type', 'text/xml'); + response.set('Server', 'Nintendo 3DS (http)'); + response.set('X-Nintendo-Date', new Date().getTime()); + + const {headers} = request; + + if ( + !headers['x-nintendo-client-id'] || + !headers['x-nintendo-client-secret'] || + !VALID_CLIENT_ID_SECRET_PAIRS[headers['x-nintendo-client-id']] || + headers['x-nintendo-client-secret'] !== VALID_CLIENT_ID_SECRET_PAIRS[headers['x-nintendo-client-id']] + ) { + return response.send(xmlbuilder.create({ + errors: { + error: { + cause: 'client_id', + code: '0004', + message: 'API application invalid or incorrect application credentials' + } + } + }).end()); + } + + return next(); +} + +module.exports = nintendoClientHeaderCheck; \ No newline at end of file diff --git a/src/middleware/pnid.js b/src/middleware/pnid.js new file mode 100644 index 0000000..5d47cde --- /dev/null +++ b/src/middleware/pnid.js @@ -0,0 +1,50 @@ +const xmlbuilder = require('xmlbuilder'); +const database = require('../database'); + +async function PNIDMiddleware(request, response, next) { + const { headers } = request; + + if (!headers.authorization || !(headers.authorization.startsWith('Bearer') || headers.authorization.startsWith('Basic'))) { + return next(); + } + + const [type, token] = headers.authorization.split(' '); + let user; + + if (type === 'Basic') { + user = await database.getUserBasic(token); + } else { + user = await database.getUserBearer(token); + } + + if (!user) { + response.status(401); + + if (type === 'Bearer') { + return response.send(xmlbuilder.create({ + errors: { + error: { + cause: 'access_token', + code: '0005', + message: 'Invalid access token' + } + } + }).end()); + } + + return response.send(xmlbuilder.create({ + errors: { + error: { + code: '1105', + message: 'Email address, username, or password, is not valid' + } + } + }).end()); + } + + request.pnid = user; + + return next(); +} + +module.exports = PNIDMiddleware; \ No newline at end of file diff --git a/src/middleware/session.js b/src/middleware/session.js new file mode 100644 index 0000000..e8b663f --- /dev/null +++ b/src/middleware/session.js @@ -0,0 +1,23 @@ +// super basic and there's probably a much better way to do this + +// this will only be used during the registration process, to track the progress of the user +// express-session uses cookies which the WiiU does not support during the registration process + +// temp, in-memory session storage +const sessionStore = {}; + +function sessionMiddlware(request, response, next) { + const ip = request.headers['x-forwarded-for'] || request.connection.remoteAddress; + + if (!sessionStore[ip]) { + sessionStore[ip] = {}; + } + + const session = sessionStore[ip]; + + request.session = session; + + return next(); +} + +module.exports = sessionMiddlware; \ No newline at end of file diff --git a/src/middleware/xml-parser.js b/src/middleware/xml-parser.js new file mode 100644 index 0000000..12fb614 --- /dev/null +++ b/src/middleware/xml-parser.js @@ -0,0 +1,35 @@ +const { document: xmlParser } = require('xmlbuilder2'); + +function XMLMiddleware(request, response, next) { + if (request.method == 'POST' || request.method == 'PUT') { + const headers = request.headers; + let body = ''; + + if ( + !headers['content-type'] || + !headers['content-type'].toLowerCase().includes('xml') + ) { + return next(); + } + + request.setEncoding('utf-8'); + request.on('data', (chunk) => { + body += chunk; + }); + + request.on('end', () => { + try { + request.body = xmlParser(body); + request.body = request.body.toObject(); + } catch (error) { + return next(); + } + + next(); + }); + } else { + next(); + } +} + +module.exports = XMLMiddleware; \ No newline at end of file diff --git a/src/models/communities.js b/src/models/communities.js new file mode 100644 index 0000000..1ec39d9 --- /dev/null +++ b/src/models/communities.js @@ -0,0 +1,64 @@ +const { Schema, model } = require('mongoose'); + +const CommunitySchema = new Schema({ + created_at: { + type: Date, + }, + empathy_count: { + type: Number, + default: 0 + }, + followers: { + type: Number, + default: 0 + }, + id: { + type: Number, + default: 0 + }, + has_shop_page: { + type: Number, + default: 0 + }, + icon: String, + title_ids: { + type: [String], + default: undefined + }, + title_id: { + type: [String], + default: undefined + }, + community_id: String, + is_recommended: { + type: Number, + default: 0 + }, + platform_id: Number, + name: String, + browser_icon: String, + CTR_browser_header: String, + WiiU_browser_header: String, + description: String, +}); + +CommunitySchema.methods.upEmpathy = async function() { + const empathy = this.get('empathy_count'); + this.set('empathy_count', empathy + 1); + + await this.save(); +}; + +CommunitySchema.methods.downEmpathy = async function() { + const empathy = this.get('empathy_count'); + this.set('empathy_count', empathy - 1); + + await this.save(); +}; + +const COMMUNITY = model('COMMUNITY', CommunitySchema); + +module.exports = { + CommunitySchema, + COMMUNITY +}; \ No newline at end of file diff --git a/src/models/endpoint.js b/src/models/endpoint.js new file mode 100644 index 0000000..41c29d8 --- /dev/null +++ b/src/models/endpoint.js @@ -0,0 +1,27 @@ +const { Schema, model } = require('mongoose'); + +const endpointSchema = new Schema({ + has_error: Number, + version: Number, + endpoint: { + host: String, + api_host: String, + portal_host: String, + n3ds_host: String + } +}); + +endpointSchema.methods.updateHosts = async function({host, api_host, portal_host, n3ds_host}) { + this.set('endpoint.host', host); + this.set('endpoint.api_host', api_host); + this.set('endpoint.portal_host', portal_host); + this.set('endpoint.n3ds_host', n3ds_host); + await this.save(); +}; + +const ENDPOINT = model('ENDPOINT', endpointSchema); + +module.exports = { + endpointSchema, + ENDPOINT +}; diff --git a/src/models/post.js b/src/models/post.js new file mode 100644 index 0000000..bea3bec --- /dev/null +++ b/src/models/post.js @@ -0,0 +1,103 @@ +const { Schema, model } = require('mongoose'); +//just testing pull requests +const PostSchema = new Schema({ + title_id: String, + screen_name: String, + body: String, + app_data: String, + painting: String, + painting_uri: String, + screenshot: String, + url: String, + search_key: { + type: [String], + default: undefined + }, + topic_tag: { + type: [String], + default: undefined + }, + community_id: String, + country_id: Number, + created_at: String, + feeling_id: Number, + id: Number, + is_autopost: { + type: Number, + default: 0 + }, + is_community_private_autopost: { + type: Number, + default: 0 + }, + is_spoiler: { + type: Number, + default: 0 + }, + is_app_jumpable: { + type: Number, + default: 0 + }, + empathy_count: { + type: Number, + default: 0 + }, + language_id: { + type: Number, + default: 1 + }, + mii: String, + mii_face_url: String, + number: { + type: Number, + default: 1 + }, + pid: Number, + platform_id: Number, + region_id: Number, + parent_post: Number, + reply_count: { + type: Number, + default: 0 + }, + verified: { + type: Boolean, + default: false + } +}); + + +PostSchema.methods.upEmpathy = async function() { + const empathy = this.get('empathy_count'); + this.set('empathy_count', empathy + 1); + + await this.save(); +}; + +PostSchema.methods.downEmpathy = async function() { + const empathy = this.get('empathy_count'); + this.set('empathy_count', empathy - 1); + + await this.save(); +}; + +PostSchema.methods.upReply = async function() { + const replyCount = this.get('reply_count'); + this.set('reply_count', replyCount + 1); + + await this.save(); +}; + +PostSchema.methods.downReply = async function() { + const replyCount = this.get('reply_count'); + this.set('reply_count', replyCount - 1); + + await this.save(); +}; + +const POST = model('POST', PostSchema); + +module.exports = { + PostSchema, + POST +}; \ No newline at end of file diff --git a/src/models/topic.js b/src/models/topic.js new file mode 100644 index 0000000..53f53ed --- /dev/null +++ b/src/models/topic.js @@ -0,0 +1,79 @@ +const { Schema, model } = require('mongoose'); +const { PostSchema } = require('./post'); + +const titleIdsSchema = new Schema({ + title_id: String, +}); + +const personSchema = new Schema({ + posts: { + type: [PostSchema], + default: undefined + } +}); + +const TopicSchema = new Schema({ + empathy_count: { + type: Number, + default: 0 + }, + has_shop_page: { + type: Number, + default: 0 + }, + icon: String, + title_ids: { + type: [titleIdsSchema], + default: undefined + }, + title_id: String, + community_id: String, + is_recommended: { + type: Number, + default: 0 + }, + name: String, + people: { + type: [personSchema], + default: undefined + }, +}); + +TopicSchema.methods.upEmpathy = async function() { + const empathy = this.get('empathy_count'); + this.set('empathy_count', empathy + 1); + + await this.save(); +}; + +TopicSchema.methods.downEmpathy = async function() { + const empathy = this.get('empathy_count'); + this.set('empathy_count', empathy - 1); + + await this.save(); +}; + +TopicSchema.pre('save', async function(next) { + if (!this.isModified('password')) { + return next(); + } + + this.set('usernameLower', this.get('username').toLowerCase()); + await this.generatePID(); + await this.generateNEXPassword(); + await this.generateEmailValidationCode(); + await this.generateEmailValidationToken(); + + const primaryHash = util.nintendoPasswordHash(this.get('password'), this.get('pid')); + const hash = bcrypt.hashSync(primaryHash, 10); + + this.set('password', hash); + next(); +}); + +const MiiverseTopic = model('MiiverseTopic', PostSchema); + +module.exports = { + TopicSchema, + MiiverseTopic +}; \ No newline at end of file diff --git a/src/models/user.js b/src/models/user.js new file mode 100644 index 0000000..5eeac4a --- /dev/null +++ b/src/models/user.js @@ -0,0 +1,152 @@ +const { Schema, model } = require('mongoose'); + +const UserSchema = new Schema({ + pid: Number, + created_at: String, + user_id: String, + birthday: Date, + country: String, + pfp_uri: String, + mii: String, + /** + * Account Status + * 0 - Fine + * 1 - Limited from Posting + * 2 - Temporary Ban + * 3 - Forever Ban + */ + account_status: { + type: Number, + default: 0 + }, + ban_lift_date: Date, + official: { + type: Boolean, + default: false + }, + favorite_communities: { + type: [String], + default: undefined + }, + profile_comment: { + type: String, + default: undefined + }, + /** + * Game Skill Level + * 1 - Beginner + * 2 - Intermediate + * 3 - Expert + */ + game_skill: { + type: Number, + default: 0 + }, + /** + * Profile Visibility settings + * 1 - Everyone + * 2 - Friends Only + * 3 - Private + */ + game_skill_visibility: { + type: Number, + default: 1 + }, + profile_comment_visibility: { + type: Number, + default: 1 + }, + birthday_visibility: { + type: Number, + default: 3 + }, + relationship_visibility: { + type: Number, + default: 2 + }, + profile_favorite_community_visibility: { + type: Number, + default: 1 + } + +}); + +UserSchema.methods.getAccountStatus = async function() { + return this.get('account_status'); +}; + +UserSchema.methods.setAccountStatus = async function(accountStatus) { + this.set('account_status', accountStatus); + await this.save(); +}; +UserSchema.methods.getBanDate = async function() { + return this.get('ban_lift_date'); +}; + +UserSchema.methods.setBanData = async function(banDate) { + this.set('ban_lift_date', banDate); + await this.save(); +}; +UserSchema.methods.getProfileComment = async function() { + return this.get('profile_comment'); +}; +UserSchema.methods.setProfileComment = async function(profileComment) { + this.set('profile_comment', profileComment); + await this.save(); +}; + +UserSchema.methods.getGameSkill = async function() { + return this.get('game_skill'); +}; + +UserSchema.methods.setGameSkill = async function(gameSkill) { + this.set('game_skill', gameSkill); + await this.save(); +}; +UserSchema.methods.getGameSkillVisibility = async function() { + return this.get('game_skill_visibility'); +}; + +UserSchema.methods.setGameSkillVisibility = async function(gameSkillVisibility) { + this.set('game_skill_visibility', gameSkillVisibility); + await this.save(); +}; +UserSchema.methods.getProfileCommentVisibility = async function() { + return this.get('profile_comment_visibility'); +}; + +UserSchema.methods.setProfileCommentVisibility = async function(profileCommentVisibility) { + this.set('profile_comment_visibility', profileCommentVisibility); + await this.save(); +}; +UserSchema.methods.getBirthdayVisibility = async function() { + return this.get('birthday_visibility'); +}; + +UserSchema.methods.setBirthdayVisibility = async function(birthdayVisibility) { + this.set('birthday_visibility', birthdayVisibility); + await this.save(); +}; +UserSchema.methods.getRelationshipVisibility = async function() { + return this.get('relationship_visibility'); +}; + +UserSchema.methods.setRelationshipVisibility = async function(accountStatus) { + this.set('relationship_visibility', accountStatus); + await this.save(); +}; +UserSchema.methods.getFavoriteCommunityVisibility = async function() { + return this.get('profile_favorite_community_visibility'); +}; + +UserSchema.methods.setFavoriteCommunityVisibility = async function(favoriteCommunityVisibility) { + this.set('profile_favorite_community_visibility', favoriteCommunityVisibility); + await this.save(); +}; + +const USER = model('USER', UserSchema); + +module.exports = { + UserSchema, + USER +}; \ No newline at end of file diff --git a/src/server.js b/src/server.js new file mode 100644 index 0000000..3a65a83 --- /dev/null +++ b/src/server.js @@ -0,0 +1,64 @@ +process.title = 'Pretendo - Miiverse'; +const express = require('express'); +const morgan = require('morgan'); +const ejs = require('ejs'); +const xmlparser = require('./middleware/xml-parser'); +const database = require('./database'); +const logger = require('./logger'); +const config = require('./config.json'); + +const { http: { port } } = config; +const app = express(); + +const juxt_web = require('./services/juxt-web'); + +app.set('etag', false); +app.disable('x-powered-by'); +app.set('view engine', 'ejs'); +app.set('views', __dirname + '\\views'); + +// Create router +logger.info('Setting up Middleware'); +app.use(morgan('dev')); +app.use(express.json()); + +app.use(express.urlencoded({ + extended: true, + limit: '5mb', + parameterLimit: 100000 +})); +app.use(xmlparser); + +// import the servers into one +app.use(juxt_web); + +// 404 handler +logger.info('Creating 404 status handler'); +app.use((request, response) => { + //logger.warn(request.protocol + '://' + request.get('host') + request.originalUrl); + response.status(404); + response.send(); +}); + +// non-404 error handler +logger.info('Creating non-404 status handler'); +app.use((error, request, response) => { + const status = error.status || 500; + + response.status(status); + + response.json({ + app: 'api', + status, + error: error.message + }); +}); + +// Starts the server +logger.info('Starting server'); + +database.connect().then(() => { + app.listen(port, () => { + logger.success(`Server started on port ${port}`); + }); +}); \ No newline at end of file diff --git a/src/services/juxt-web/index.js b/src/services/juxt-web/index.js new file mode 100644 index 0000000..30449bc --- /dev/null +++ b/src/services/juxt-web/index.js @@ -0,0 +1,27 @@ +const express = require('express'); +const subdomain = require('express-subdomain'); +const logger = require('../../logger'); +const routes = require('./routes'); + +const router = express.Router(); + +const portal = express.Router(); +const ctr = express.Router(); + +// Create subdomains +logger.info('[JUXT-WEB] Creating \'Wii U\' subdomain'); +router.use(subdomain('portal.olv', portal)); + +logger.info('[JUXT-WEB] Creating \'3DS\' subdomain'); +router.use(subdomain('ctr.olv', ctr)); + +// Setup routes +portal.use('/titles/show', routes.PORTAL_SHOW); +portal.use('/communities', routes.PORTAL_COMMUNITIES); +portal.use('/', routes.PORTAL_WEB); + +ctr.use('/titles/show', routes.CTR_SHOW); +ctr.use('/communities', routes.CTR_COMMUNITIES); +ctr.use('/', routes.CTR_WEB); + +module.exports = router; \ No newline at end of file diff --git a/src/services/juxt-web/routes/ctr/communities.js b/src/services/juxt-web/routes/ctr/communities.js new file mode 100644 index 0000000..802f3a4 --- /dev/null +++ b/src/services/juxt-web/routes/ctr/communities.js @@ -0,0 +1,62 @@ +var express = require('express'); +var xml = require('object-to-xml'); +const database = require('../../../../database'); +//const util = require('../../../../util/authentication'); +const ejs = require('ejs'); +var router = express.Router(); + +/* GET discovery server. */ +router.get('/', function (req, res) { + res.header('X-Nintendo-WhiteList','1|http,youtube.com,,2|https,youtube.com,,2|http,.youtube.com,,2|https,.youtube.com,,2|http,.ytimg.com,,2|https,.ytimg.com,,2|http,.googlevideo.com,,2|https,.googlevideo.com,,2|https,youtube.com,/embed/,6|https,youtube.com,/e/,6|https,youtube.com,/v/,6|https,www.youtube.com,/embed/,6|https,www.youtube.com,/e/,6|https,www.youtube.com,/v/,6|https,youtube.googleapis.com,/e/,6|https,youtube.googleapis.com,/v/,6|http,maps.googleapis.com,/maps/api/streetview,2|https,maps.googleapis.com,/maps/api/streetview,2|http,cbk0.google.com,/cbk,2|https,cbk0.google.com,/cbk,2|http,cbk1.google.com,/cbk,2|https,cbk1.google.com,/cbk,2|http,cbk2.google.com,/cbk,2|https,cbk2.google.com,/cbk,2|http,cbk3.google.com,/cbk,2|https,cbk3.google.com,/cbk,2|https,.cloudfront.net,,2|https,www.google-analytics.com,/,2|https,stats.g.doubleclick.net,,2|https,www.google.com,/ads/,2|https,ssl.google-analytics.com,,2|http,fonts.googleapis.com,,2||fonts.googleapis.com,,2'); + database.connect().then(async e => { + let popularCommunities = await database.getMostPopularCommunities(9); + let newCommunities = await database.getNewCommunities(6); + res.render('ctr_communities.ejs', { + // EJS variable and server-side variable + popularCommunities: popularCommunities, + newCommunities: newCommunities + }); + }).catch(error => { + res.set("Content-Type", "application/xml"); + res.statusCode = 400; + response = { + result: { + has_error: 1, + version: 1, + code: 400, + error_code: 15, + message: "SERVER_ERROR" + } + }; + res.send("\n" + xml(response)); + }); +}); + +router.get('/*/new', function (req, res) { + res.header('X-Nintendo-WhiteList','1|http,youtube.com,,2|https,youtube.com,,2|http,.youtube.com,,2|https,.youtube.com,,2|http,.ytimg.com,,2|https,.ytimg.com,,2|http,.googlevideo.com,,2|https,.googlevideo.com,,2|https,youtube.com,/embed/,6|https,youtube.com,/e/,6|https,youtube.com,/v/,6|https,www.youtube.com,/embed/,6|https,www.youtube.com,/e/,6|https,www.youtube.com,/v/,6|https,youtube.googleapis.com,/e/,6|https,youtube.googleapis.com,/v/,6|http,maps.googleapis.com,/maps/api/streetview,2|https,maps.googleapis.com,/maps/api/streetview,2|http,cbk0.google.com,/cbk,2|https,cbk0.google.com,/cbk,2|http,cbk1.google.com,/cbk,2|https,cbk1.google.com,/cbk,2|http,cbk2.google.com,/cbk,2|https,cbk2.google.com,/cbk,2|http,cbk3.google.com,/cbk,2|https,cbk3.google.com,/cbk,2|https,.cloudfront.net,,2|https,www.google-analytics.com,/,2|https,stats.g.doubleclick.net,,2|https,www.google.com,/ads/,2|https,ssl.google-analytics.com,,2|http,fonts.googleapis.com,,2||fonts.googleapis.com,,2'); + let community_id = req.originalUrl.replace('/communities/', '').replace('/new','').trim(); + database.connect().then(async e => { + let community = await database.getCommunityByID(community_id); + let newPosts = await database.getPostsByCommunity(community, 25); + res.render('ctr_community.ejs', { + // EJS variable and server-side variable + community: community, + newPosts: newPosts + }); + }).catch(error => { + res.set("Content-Type", "application/xml"); + res.statusCode = 400; + response = { + result: { + has_error: 1, + version: 1, + code: 400, + error_code: 15, + message: "SERVER_ERROR" + } + }; + res.send("\n" + xml(response)); + }); +}); + +module.exports = router; diff --git a/src/services/juxt-web/routes/ctr/show.js b/src/services/juxt-web/routes/ctr/show.js new file mode 100644 index 0000000..e5fbbf6 --- /dev/null +++ b/src/services/juxt-web/routes/ctr/show.js @@ -0,0 +1,35 @@ +var express = require('express'); +var xml = require('object-to-xml'); +const database = require('../../../../database'); +//const util = require('../../../../util/authentication'); +const ejs = require('ejs'); +var router = express.Router(); + +/* GET discovery server. */ +router.get('/', function (req, res) { + res.header('X-Nintendo-WhiteList','1|http,youtube.com,,2|https,youtube.com,,2|http,.youtube.com,,2|https,.youtube.com,,2|http,.ytimg.com,,2|https,.ytimg.com,,2|http,.googlevideo.com,,2|https,.googlevideo.com,,2|https,youtube.com,/embed/,6|https,youtube.com,/e/,6|https,youtube.com,/v/,6|https,www.youtube.com,/embed/,6|https,www.youtube.com,/e/,6|https,www.youtube.com,/v/,6|https,youtube.googleapis.com,/e/,6|https,youtube.googleapis.com,/v/,6|http,maps.googleapis.com,/maps/api/streetview,2|https,maps.googleapis.com,/maps/api/streetview,2|http,cbk0.google.com,/cbk,2|https,cbk0.google.com,/cbk,2|http,cbk1.google.com,/cbk,2|https,cbk1.google.com,/cbk,2|http,cbk2.google.com,/cbk,2|https,cbk2.google.com,/cbk,2|http,cbk3.google.com,/cbk,2|https,cbk3.google.com,/cbk,2|https,.cloudfront.net,,2|https,www.google-analytics.com,/,2|https,stats.g.doubleclick.net,,2|https,www.google.com,/ads/,2|https,ssl.google-analytics.com,,2|http,fonts.googleapis.com,,2||fonts.googleapis.com,,2'); + database.connect().then(async e => { + let popularCommunities = await database.getMostPopularCommunities(9); + let newCommunities = await database.getNewCommunities(6); + res.render('ctr_show.ejs', { + // EJS variable and server-side variable + popularCommunities: popularCommunities, + newCommunities: newCommunities + }); + }).catch(error => { + res.set("Content-Type", "application/xml"); + res.statusCode = 400; + response = { + result: { + has_error: 1, + version: 1, + code: 400, + error_code: 15, + message: "SERVER_ERROR" + } + }; + res.send("\n" + xml(response)); + }); +}); + +module.exports = router; diff --git a/src/services/juxt-web/routes/ctr/web.js b/src/services/juxt-web/routes/ctr/web.js new file mode 100644 index 0000000..81f3293 --- /dev/null +++ b/src/services/juxt-web/routes/ctr/web.js @@ -0,0 +1,22 @@ +var express = require('express'); +var router = express.Router(); +var path = require('path'); + +/* GET discovery server. */ +router.get('/css/juxt.css', function (req, res) { + res.sendFile('css/juxt.css', {root: path.join(__dirname, '../../../../webfiles/ctr/')}); +}); + +router.get('/js/juxt.js', function (req, res) { + res.sendFile('js/juxt.js', {root: path.join(__dirname, '../../../../webfiles/ctr/')}); +}); + +router.get('/icons/Icon-feather-search.svg', function (req, res) { + res.sendFile('icons/Icon-feather-search.svg', {root: path.join(__dirname, '../../../../webfiles/ctr/')}) +}); + +router.get('/icons/mario-kart.jpg', function (req, res) { + res.sendFile('icons/mario-kart.jpg', {root: path.join(__dirname, '../../../../webfiles/ctr/')}); +}); + +module.exports = router; diff --git a/src/services/juxt-web/routes/index.js b/src/services/juxt-web/routes/index.js new file mode 100644 index 0000000..597e75e --- /dev/null +++ b/src/services/juxt-web/routes/index.js @@ -0,0 +1,8 @@ +module.exports = { + PORTAL_SHOW: require('./portal/show'), + PORTAL_WEB: require('./portal/web'), + PORTAL_COMMUNITIES: require('./portal/communities'), + CTR_SHOW: require('./ctr/show'), + CTR_WEB: require('./ctr/web'), + CTR_COMMUNITIES: require('./ctr/communities'), +}; \ No newline at end of file diff --git a/src/services/juxt-web/routes/portal/communities.js b/src/services/juxt-web/routes/portal/communities.js new file mode 100644 index 0000000..cca4548 --- /dev/null +++ b/src/services/juxt-web/routes/portal/communities.js @@ -0,0 +1,89 @@ +var express = require('express'); +var xml = require('object-to-xml'); +const database = require('../../../../database'); +//const util = require('../../../../util/authentication'); +const ejs = require('ejs'); +var router = express.Router(); + +/* GET discovery server. */ +router.get('/', function (req, res) { + res.header('X-Nintendo-WhiteList','1|http,youtube.com,,2|https,youtube.com,,2|http,.youtube.com,,2|https,.youtube.com,,2|http,.ytimg.com,,2|https,.ytimg.com,,2|http,.googlevideo.com,,2|https,.googlevideo.com,,2|https,youtube.com,/embed/,6|https,youtube.com,/e/,6|https,youtube.com,/v/,6|https,www.youtube.com,/embed/,6|https,www.youtube.com,/e/,6|https,www.youtube.com,/v/,6|https,youtube.googleapis.com,/e/,6|https,youtube.googleapis.com,/v/,6|http,maps.googleapis.com,/maps/api/streetview,2|https,maps.googleapis.com,/maps/api/streetview,2|http,cbk0.google.com,/cbk,2|https,cbk0.google.com,/cbk,2|http,cbk1.google.com,/cbk,2|https,cbk1.google.com,/cbk,2|http,cbk2.google.com,/cbk,2|https,cbk2.google.com,/cbk,2|http,cbk3.google.com,/cbk,2|https,cbk3.google.com,/cbk,2|https,.cloudfront.net,,2|https,www.google-analytics.com,/,2|https,stats.g.doubleclick.net,,2|https,www.google.com,/ads/,2|https,ssl.google-analytics.com,,2|http,fonts.googleapis.com,,2||fonts.googleapis.com,,2'); + var isAJAX = ((req.query.ajax+'').toLowerCase() === 'true') + database.connect().then(async e => { + let popularCommunities = await database.getMostPopularCommunities(9); + let newCommunities = await database.getNewCommunities(6); + if(isAJAX) { + res.render('portal_communities_ajax.ejs', { + // EJS variable and server-side variable + popularCommunities: popularCommunities, + newCommunities: newCommunities + }); + } + else { + res.render('portal_communities.ejs', { + // EJS variable and server-side variable + popularCommunities: popularCommunities, + newCommunities: newCommunities + }); + } + + }).catch(error => { + res.set("Content-Type", "application/xml"); + res.statusCode = 400; + response = { + result: { + has_error: 1, + version: 1, + code: 400, + error_code: 15, + message: "SERVER_ERROR" + } + }; + res.send("\n" + xml(response)); + }); +}); + +router.get('/*/new', function (req, res) { + res.header('X-Nintendo-WhiteList','1|http,youtube.com,,2|https,youtube.com,,2|http,.youtube.com,,2|https,.youtube.com,,2|http,.ytimg.com,,2|https,.ytimg.com,,2|http,.googlevideo.com,,2|https,.googlevideo.com,,2|https,youtube.com,/embed/,6|https,youtube.com,/e/,6|https,youtube.com,/v/,6|https,www.youtube.com,/embed/,6|https,www.youtube.com,/e/,6|https,www.youtube.com,/v/,6|https,youtube.googleapis.com,/e/,6|https,youtube.googleapis.com,/v/,6|http,maps.googleapis.com,/maps/api/streetview,2|https,maps.googleapis.com,/maps/api/streetview,2|http,cbk0.google.com,/cbk,2|https,cbk0.google.com,/cbk,2|http,cbk1.google.com,/cbk,2|https,cbk1.google.com,/cbk,2|http,cbk2.google.com,/cbk,2|https,cbk2.google.com,/cbk,2|http,cbk3.google.com,/cbk,2|https,cbk3.google.com,/cbk,2|https,.cloudfront.net,,2|https,www.google-analytics.com,/,2|https,stats.g.doubleclick.net,,2|https,www.google.com,/ads/,2|https,ssl.google-analytics.com,,2|http,fonts.googleapis.com,,2||fonts.googleapis.com,,2'); + let community_id = req.originalUrl.replace('/communities/', '').replace('/new','').trim(); + var isAJAX = ((req.query.ajax+'').toLowerCase() === 'true') + if(isAJAX) + community_id = community_id.substring(0, community_id.indexOf('?')); + database.connect().then(async e => { + let community = await database.getCommunityByID(community_id.substring(0, community_id)); + let newPosts = await database.getNewPostsByCommunity(community, 100); + let totalNumPosts = await database.getNumberPostsByCommunity(community); + if(isAJAX) { + res.render('portal_community_ajax.ejs', { + // EJS variable and server-side variable + community: community, + newPosts: newPosts, + totalNumPosts: totalNumPosts + }); + } + else { + res.render('portal_community.ejs', { + // EJS variable and server-side variable + community: community, + newPosts: newPosts, + totalNumPosts: totalNumPosts + }); + } + }).catch(error => { + console.error(error); + res.set("Content-Type", "application/xml"); + res.statusCode = 400; + response = { + result: { + has_error: 1, + version: 1, + code: 400, + error_code: 15, + message: "SERVER_ERROR" + } + }; + res.send("\n" + xml(response)); + }); +}); + +module.exports = router; diff --git a/src/services/juxt-web/routes/portal/show.js b/src/services/juxt-web/routes/portal/show.js new file mode 100644 index 0000000..4d3dfd0 --- /dev/null +++ b/src/services/juxt-web/routes/portal/show.js @@ -0,0 +1,35 @@ +var express = require('express'); +var xml = require('object-to-xml'); +const database = require('../../../../database'); +//const util = require('../../../../util/authentication'); +const ejs = require('ejs'); +var router = express.Router(); + +/* GET discovery server. */ +router.get('/', function (req, res) { + res.header('X-Nintendo-WhiteList','1|http,youtube.com,,2|https,youtube.com,,2|http,.youtube.com,,2|https,.youtube.com,,2|http,.ytimg.com,,2|https,.ytimg.com,,2|http,.googlevideo.com,,2|https,.googlevideo.com,,2|https,youtube.com,/embed/,6|https,youtube.com,/e/,6|https,youtube.com,/v/,6|https,www.youtube.com,/embed/,6|https,www.youtube.com,/e/,6|https,www.youtube.com,/v/,6|https,youtube.googleapis.com,/e/,6|https,youtube.googleapis.com,/v/,6|http,maps.googleapis.com,/maps/api/streetview,2|https,maps.googleapis.com,/maps/api/streetview,2|http,cbk0.google.com,/cbk,2|https,cbk0.google.com,/cbk,2|http,cbk1.google.com,/cbk,2|https,cbk1.google.com,/cbk,2|http,cbk2.google.com,/cbk,2|https,cbk2.google.com,/cbk,2|http,cbk3.google.com,/cbk,2|https,cbk3.google.com,/cbk,2|https,.cloudfront.net,,2|https,www.google-analytics.com,/,2|https,stats.g.doubleclick.net,,2|https,www.google.com,/ads/,2|https,ssl.google-analytics.com,,2|http,fonts.googleapis.com,,2||fonts.googleapis.com,,2'); + database.connect().then(async e => { + let popularCommunities = await database.getMostPopularCommunities(9); + let newCommunities = await database.getNewCommunities(6); + res.render('portal_show.ejs', { + // EJS variable and server-side variable + popularCommunities: popularCommunities, + newCommunities: newCommunities + }); + }).catch(error => { + res.set("Content-Type", "application/xml"); + res.statusCode = 400; + response = { + result: { + has_error: 1, + version: 1, + code: 400, + error_code: 15, + message: "SERVER_ERROR" + } + }; + res.send("\n" + xml(response)); + }); +}); + +module.exports = router; diff --git a/src/services/juxt-web/routes/portal/web.js b/src/services/juxt-web/routes/portal/web.js new file mode 100644 index 0000000..153449e --- /dev/null +++ b/src/services/juxt-web/routes/portal/web.js @@ -0,0 +1,22 @@ +var express = require('express'); +var router = express.Router(); +var path = require('path'); + +/* GET discovery server. */ +router.get('/css/juxt.css', function (req, res) { + res.sendFile('css/juxt.css', {root: path.join(__dirname, '../../../../webfiles/portal/')}); +}); + +router.get('/js/juxt.js', function (req, res) { + res.sendFile('js/juxt.js', {root: path.join(__dirname, '../../../../webfiles/portal/')}); +}); + +router.get('/icons/Icon-feather-search.svg', function (req, res) { + res.sendFile('icons/Icon-feather-search.svg', {root: path.join(__dirname, '../../../../webfiles/portal/')}) +}); + +router.get('/icons/mario-kart.jpg', function (req, res) { + res.sendFile('icons/mario-kart.jpg', {root: path.join(__dirname, '../../../../webfiles/portal/')}); +}); + +module.exports = router; diff --git a/src/views/ctr_communities.ejs b/src/views/ctr_communities.ejs new file mode 100644 index 0000000..45125af --- /dev/null +++ b/src/views/ctr_communities.ejs @@ -0,0 +1,74 @@ +
+ Check out the communities for the games that you play or games that you're curious about! +
+|
+
+
+ <%= popularCommunities[i].name %>+<%= popularCommunities[i].followers %> followers+ |
+
|
+
+
+ <%= popularCommunities[i].name %>+<%= popularCommunities[i].followers %> followers+ |
+
|
+
+
+ <%= newCommunities[j].name %>+<%= newCommunities[j].followers %> followers+ |
+
|
+
+
+ <%= newCommunities[j].name %>+<%= newCommunities[j].followers %> followers+ |
+
<%= person.name %>
+<%= person.age %>
+|
+
+
+ <%= popularCommunities[i].name %>+<%= popularCommunities[i].followers %> followers+ |
+ <% if( (i + 1) % 3 === 0) { %>
+
|
+
+
+ <%= newCommunities[j].name %>+<%= newCommunities[j].followers %> followers+ |
+ <% if( (j + 1) % 3 === 0) { %>
+
|
+
+
+ <%= popularCommunities[i].name %>+<%= popularCommunities[i].followers %> followers+ |
+ <% if( (i + 1) % 3 === 0) { %>
+
|
+
+
+ <%= newCommunities[j].name %>+<%= newCommunities[j].followers %> followers+ |
+ <% if( (j + 1) % 3 === 0) { %>
+
|
+
+
+ Followers+<%= community.followers %>+ |
+
+
+
+ Posts+<%=totalNumPosts%>+ |
+
+
+
+ Tags+N/A+ |
+
+ Recent posts+ |
+
+ Popular posts+ |
+
+ Verified posts+ |
+
|
+
+
+ Followers+<%= community.followers %>+ |
+
+
+
+ Posts+<%=totalNumPosts%>+ |
+
+
+
+ Tags+N/A+ |
+
+ Recent posts+ |
+
+ Popular posts+ |
+
+ Verified posts+ |
+
+ Recent posts+ |
+
+ Popular posts+ |
+
+ Verified posts+ |
+
Screenshot Only:
+Painting Only:
+Video Only:
+ +Text Only:
+Screenshot and Body:
+Screenshot and Painting:
+Video and Body:
+ + +Video and Painting:
+ +|
+
+
+ <%= popularCommunities[i].name %>+<%= popularCommunities[i].followers %> followers+ |
+ <% if( (i + 1) % 3 === 0) { %>
+
|
+
+
+ <%= newCommunities[j].name %>+<%= newCommunities[j].followers %> followers+ |
+ <% if( (j + 1) % 3 === 0) { %>
+
G z>13Rd$24aG@%AhSKf+gW+UTR@dva80DR`V~k1VP6__)t~a&CdtJaHXm5i%fS(Z)+r zTH}?$Vd_`A4qvo_39jWSKK3KdM4lCxU-c z^J m9EX|_#7Pw`!;3d%ov%!J4LNLR_&oCHXJI&TmJn2i z0Mlci&%b$vC5}k7F*q?g{v8dXZ%8U`l{?#nqohr&!JwWd8%|uJq(Dizx)(GvIWuHx znz*sZ(>2y1OBI23pnIS^97`81 ~XU9VLiovXn+0-Y@|A f%Ff@XdzlkYTu>D@%BvP=Y8m zZzs4my{AtzBANx2ZP)^nYi8?zGO0}BUGW`Ro>S$yQ!tRq{)?2d;d8Bfskenm6B~<5 zQj-0KQjooTo+UXmA&JGl)*p&}k-d726-Y9!MQiWQ?w=fyj$dk{k{B{k2%iSk2QQ|$ z%n)L1Gn`}fl2a9!i$~$A+*Upg#)G;CZl~?nMs++&=6`U=Ihdw5$eD*K=3lK>8Ei5z zZ@5OS$zX;nY-0s-#xKm^ds6<{w=ADr*RlxPZu96QXD2u{{#=mr$4>Ll)i6d;m?57a zZ(LgOu=HY~NmS>^ix32x&5Y>m#4&Cl{JI7;$8BUo{zOJiATDvRkP8%!lP65aTm*V+ z6>izFSRR?<`wPgj1_R|B34iu#72YdH2^#YJ1>8iI#D)?a;~_Koy&8DK2Hi2aN^u6X zE1`X-!Z||xiJeQwG&6!$Zu#YQ=8idyUn^}mxpDPA6Uu!}rM^r_Zwis|bfq6|Hsf*k zX=WOWJo2Tnh2gT3KO<^e^k7;D$Vl)0Y)Q}%7E$*_4}U*E1q&GPE7CV-kg*m^*0qU# zH$b9D<7dLur=`$(7l;?FYY-9+Hn#Xt_Iyl%I|D<$8gxFkFnQcAV!A4hrHPP$G#6WW zE!T3!9RVkWLYAsZH=Nxk(3Z=9?d-kP(RgwHt-}_4_8h!mrlbG8S1*q;kjWl?ll)qW zG44z}k8h6<{TqjW$^z=ukF}LrvcVQgYIxJMy)5aOA?lF(pcFE_{x;lOiWob0MyCa2 zxQpaE1$ ?L`LrY(bn$f2FPxPYq*&Y|5MO;)Q zSbHvl4KeL%+0^jCGYk`HN}LQ4+Ug0lf+}UnqY^Hjmrc_N5vVM~U^oLx_ZV=Q2T8OX zuIr%Upe%X{&HT^50L0_3;X|AWH6(bI%Ys^+IVzA6T4>Wr-~O6{LZV}%iei8k9}O$l zRf|`cNUpz@=t$g!Hf^AT6jT0$aajG{n6H=d`Rtn<9Ev^ZnL+l&&b?ZKw@27~Ed#0i zN%opXI7pf4PhHK9rEB>`B^$2hX%F`VC!HkK9@08=@3xG-{HlGHo>Y=rX~%rJcWjQ% z4b(NS@1Xm!N>*_|#%x`^I3KLCSwmEj6g{sN&f{m1LZE<5QGBvnW0p0NEFM@9Cs3&X zV;gLR9zhufwc6KWz~N##`E0snaPuAh;9}~5HuZ;D@O;&-Y6|%k0n1m)NrMhqw26`~ zdHJC};o6xApw3=cd k4b|hjx9L2-snJ1bDSjqOUp74#q{5I4Fm6uEUWJJ}kpKB1Lx0|$AR2z! zCYn@Wip6>uhNR&F!vz^A{UcPX$q8vv+e3+t`k=yrcOR7)Ti>rrMToJWvr1pFbAMMO zJ-8XN)c~`g=1x&!u{v(>gxyx9kZ;Sr-sDE5 gV`xwpk(%y8?yYADZPY`t&ycZ*FTpe@XxJb;6Nv4%XB6@IV z?PEBvNSxE?H|e44zYY#Z)-$0kN2T(d4XH-3UvD^@`@@GfJCOHmnjcLZ6*`D z%-6`HhF2^_6Y|X$Z+Pq>x|1KS@_w*wPa_H>>1DF{+Fz|`l4 5Fn~HKj^Q97DE5f*8(J0tajI$Y3JUVi@>nJ#eX9kaou6kyR%^x7-kr?;0`Paj zaO1)mY^`zl+8U>EBE#*o1SuWQBElCWw#i?tX-u4GX%#19u<7>(^{@Saa=+r{*?Unu z; iX2R-S>Pnv@ymJY2lW{_}U!>_X3jm(>yohcWS#uCa>$xIx#qfVWU-1i}vP5U- zwgXnTyj4p>GbpQ?0>bvXS&LgT#W2BP&QhaES>0u|FOjI#WnS~%taZ=UKQ=@}Z|dgW zBZr0JHdML1jAZlmN&+MTWY;Y$vglHaG7|6sF%0mPP?=i)c0OYr=uS UXq=6pT< zIY*w`Zja(AjS_mvJu;m8^WKupa8NiJ%uFqH^=VwTaC=iVZDz-BOT4Jvc35iNM!C7{ zu{aziv#=8h=c5Z^e^e;rO5oa!MX=qvZ9FF^$gK!cS~%!z$Gq#wkwgurh9~*f4&I&( zeKm~d2vok~h!#5~dI=JB;ovc5`0=VTn%>OuXW&-yBeQOIug)(MYo@%EM827{ZEKh@ z>k${Xy%st;&7cYNuQF Ep{fYX6v)UaN zfeP_~O4(*n)VLG``~BX9Jo=#b+Wq(A1P?dacm{p@ncH?v+?yYA73ra0(nWs~+`*sK zXC;k@BXMZ$wTL9Q4Voj0_vr?&5C?w6#i&s}q1e-k9l)_0ljtq4z|H1vx-Ph29{%AK z_4^R5ebdThhpnEy@42>XTV%s2-pkx=Vd)$9PB`*$OQkJE3F8p{W#1 sB^Ji&9v%%lwKbW>{ zaddLH1u^QgtOZ%}t39eTdJK}Jf{N*@>$$;jpDX8}B}|Gj_#sco9C7OY;=goKUWS`< zJvpvE74g!Af }4g$!%t*5+@ha=YXQozX0U{$ QBHh1^*-@^w!J6cV@n9fu`h6#xJ3y!c|XOFAeMCim1(<( zGcj+5wL{*C?mFeWPV}})r`}g{92&utGkQ-a0~cS=YH|`R&vrs4mSwXB?xXVo*Cl=l z%2#iyS-a8udm+=lej(WiU-(vk+>o~L2^DF5gy)D!V-vU?ZmJ3dnJw}dZ*J_uB^!K( zMd_{2N~m>D?$c@_lY8~U?v;XXN=wLdX8>|o?eyrTTK0|+>lF3pBnHjZLV5VEK3#^u zd~5qK51)@mMXR)tC|@FoNycuuT4tD*A0L93g@*4fYV&)#$7gOhmmpbDiJm{>ElyDs z2hsW6SWPsRopd>FEqLzL8%qmc+7(Vcd(^e=+c#FCobk|wFLOT$2*h_z!*UEdFfbG* z67%n g6FOn9R;N(kaEu?lhnm<&|fCQjr55^Po1 zBW)ZAFpR8?_&dE0y;AuOFVVZ(hwdso+LhN|_|mOy{d}}3D*pn283vpsU|u8;Ei;#z zW+A|xf|{G>27PpLafT23TYn8ce_k!4$to0|HjxCG0|CzN!h%_cNlQ-`vEq1y+RK1< z^2Z1IuMQU*A!G%{m+^Bv*~MG$d|j@reQq1ZC%Uu#t>%+0bZ&BU6~}|1FBA;nCge9d zhK8AOv@svB=m%C46U@pne~wxd4DjVfE1}fk^iaL^QCX#Nm=9|xd^Wltj rmGhO-gut?If MyP_~fi|Ab*u`=HF`~;yynr%AJ9#vd^n5l8%#BvAEq#FKCxlskSU-Ph* z3xdWFhePqox4AogA$Q~J(G{P+01%Y}vA*J|t@VcBy_>9pGF6%Ra{;l0aVdxBoHKWD z5Y^3| DY*sSZ^|%+F$5uS(NOQewW5W7_KpFuQfmFz$-aC|bYy1n>*9)%YSP z+7|CjT64chFnB3@h?R$yPTeY9>-RTh_lXpB4RV2785PiZT-KA+=C*(G_0 FJ>_`u@HU;w $0R`is)?-m<}4LQ(m0u!ffV<9hu$1c3Jp)@BMIah{{{&>>Z48BM{1$KcbLAm z((NB^rpmWdm)FpghDf^Q#c^olS1@7A!OTx2Fnz*{N>R^7`OHIi{3&Q!Oz%V}NP&xc zaFWlT-de$-KvwDm*FeuSTJd+U |P zso-mda!aB`f!~w1VigR#8}6o1%;Nurx%-SOsfIYPF2VkMny6=1hw9Hgy&_a4i}{*s zJ;jVJEu+WOZM-4*mh%J(@nH4WJ%t$lByYm(S3o=~T(NGD$EsC*LCpBY{Cc515*N9= zO*FMP8g*_Aomr8GO;P-)k{e-~Te2jB)|mO(TEZ|>el2LBJKsxa0v*{-%#|#$R?CQA zUWTiu0jpU^jk8^?javXq3rc03d%k$zrC#*$HtS{1UpM~VurTnxBw0QR%dLW#PVI3V z8 8~GHTI&!Q!XMkF)QhW>S1E#; zwxF$9#>EoTsM=L5TBqzm8YbWMO77isUH0DTdYsWwex1Z>_(*o?akW7^1v2B&>!qkc z`ayQEIAear9?uNZTS`r0&scZ x*;t21uk%()PZC!5eKmgmnZy&|N+gQqjvF5I7K5FKrH_N4n4K;W;e@tB!C zviABD{JM4v%p>+L;W{m*hl-MKxuyDbrw4*2)8+(lCmX|VTZ_0B@~}NLYdtA61d{u) zerhfmzgDJ;RD>%pEH2^*#y4clE`fDl!*)+}(rr1b3vpr7$aZ+=$Ae*`lk5TU{GVD8 zRf)oy 1J;I7qz7-4L0yQLFTrbQkoPEl>Ahrhq|Vx&ip}SIAtwDxGsh zpimiPDjv28Uew?3V>j!<95lWU97k2}<6Zm_)f+{gD40N{<}1QB+%>Ye688iFu)#uG zwN@ t$yi0$2y+z z@so-}Y7PGj*k3y;C5f$W+-v=}Szw7{;m><*ZNp(VH}PV^vHlB~FzA$SlbLT!BT&~} zh`Ny1N}N=`R6aM7Jjk5ua)=3E9alT&H8V7U=}AUhs|%~n5JV|ZEKky_>8C_abJngA zm(|UQk#Q9n51DspVNFNA$ZZ1VCTlD1T>`vJL0Jm-Ju<3VE$q52m?SrILAF{K$8#m5 zy!Q9iXkQqh0^=@GLC0BS1=)@d6`SO)P7YM7Rc-izzDx$4Xi?z{sebWy=$4cb(HUrb zYq@)?ffXI7*sH6n6@&^bv~^mgjJz-El})VoTliwCgSmLoxi|C_CiuAf1qbFICPUou zIO4TRT{=pOa70bSP6L(rglzo>8r8cPTGJgPzkBWobE*#^P9M)OV6`5kE>Rmt)iYCT z9x`fC#YYjojU2n9+GW(y{{?`3{tp1@Ko-AW_9p%8i}8w| vhOEw{u{^T`xz(=De` z3KJ;Kx$|j9$yLeItC4u*Ooi6_fDyequgW^Cni$zUF}j+sru{OcK1)4$Yj&}aJ-edJ zBi+-dM2$`O?8Lb%FZSmfPl&t3fRTF)gfA$j?E&(SedvDqe|PvL(bhHDms5t?o!PP3 z3RkOK9xSZZigeh@4E5r#3t5UAFy6q*E>vy{)Wlqs@z}WQrhz>e2}M#6LmClG$&)5R zztj+RwHm}ylGKnw5k_w6PD{!_Q5P{KVPl`ue+xg6olG7`&9v#3(A6~^U}aVwNCkzJ znHnhQlavbNT!yuWuWK=7?d1Aaw~nvS>C>V{rv0hiZ4s+;vo>CTA%MEY+T; ^_60!NmkK1s(c>e(M z{{VX|fa*lcX_P?Zc2DEO88Hi~AI~l~_&a4|;-#m@D+q{1^tyED)W62=ozW7pHk%`D z2eyzj72U#0$M#`HIppe$V_2wgn@J8n+=~;_g=Ok=_BsSKsz%-jiFE{VK`S9=b*4_b za>th*D_n=Eam-^CBKfv!ltf^-xV(36#)C0wk!86oOMLmGOqnud$-6RSC{v?f |2QAKY*Q?g2$44yL!xz z-(E=pRLRIHyz$OG-8v;Af7);C$xkkt{e>xWI_=2l#0P5ZBIQZ`%yffNX>Um)7ZzNW z0yB6Xw5=YT-a?KDvUUpP#s(Dbom!SS2d1*JHg!fy#7!vVaitMA6OKsq?g_yVtY$t- zve$+ TTYfW3BDp%V zF4P|14F{C1w6R!^YBVX6FH09RfkEC`TkyJq ~KlO|2slO{vj 1z^82u2{jd=uCf(B>rj zvxWMAg))uYO0>RMQ>RX({C|wWaJ6cEHLDwPcO@U8!Aar?_hMm-P9lUIFfIy?Fy(ln z2!m@bb{Yx7NL_!7jowXG7qEEzZOZ~3GnOXA;j(oIqmUJcD&-x>-3^9KI(atMqvHLU zGGxh -wej4sv+ZT}4&Zn-p)?54mLRb9_%o<#G!R(-`_9 zEW+K0S=GFB5{_FPVwtp(3L7#evS=kDMd>r9Oqnud$&(>Mft2#c1X NrUbFL8(9Lu^zJhl-S)TMKw@jSC}f{0g`T8fJd-$pl$%WHlO{}= zGGr(jN^sYYDb;l0Y>q ()k36#EzXmXZjzsY}zt%D<#Iq$2273SZgCR z4$SE5LP)76ipVNJPbN<6a7B9C&m_{kvH}DtlPqZRwb|3De{A2ktYQgbj;umWAC*^y zg)ppjXR$MCy-dab05Kf;M`abI^3z*Eb<|g#Lv@{FoUPfd7*~U9Y26W{nVB8n2Z8y; z+1qMp{T3SQm8^C`1SEyiI%LU{As13vs|vH!`1qCHsL0CJXPU&U=un|hj^7df-B^!- zzt;Z%gi0~w@|P!&*cxNzX`P61)}FMrE`)$2FWOn71(?pkU9RK~#Fp(8&Ll`k(3z%^ zMAJy2l5;9QA|VMZE^fB#PNwwMKOIm^G^y7kl9njiD+36-$O4BZKo{~-6 D^KYxvoo^l0|0I3XZ+oDy|~9I)9>QwxE&S zWN}pPH&D{Mh)tGd{biT+vQL|`q|0OpNj0gE#;(BV9cWFXr#!Es0u>W`p-9*P-KBD8 za|ahs^*RA_DNoXzw sQ7orX&t2{H1-njGosGzi0*V<;Lyfv> zEG>soVL_Bw%7CMaF#35Z4T+9-1($@aMFe&6vD>y%ypubwOZ?qGx_J}6-xnCxj;-DW zp5-XTYQ!+oob1$*;*M!Yh+~DI$9Wf-9R8t#TP<3o5O> =-Z}{*;|IasNV&K+h8#a ms(c|S=~{(PFhL1*4pHO8W=h1c`+7{`x`qaWCD;d($bO%(ehEL&&9%VOr%&5! zYip9^oYTt4=VnJ~Nnza_&Wcn >bqm@ZAIoF((I}Bvqah2pZHli0-j? Z4<)T!m)gfGS|_RsNUeiR0>x5Fib- zg}%?&n`~Y7wwc|OR}5NXb0<#e-8m n*+B;<&ZnmG|l zo<~k=Xo*c~7}U8~RM{C_S-)W1oQYNSJ#TCtj?r_Kza5+MPPVci%;;7(_1;qU>LcMN zhK$a`6iw0>w1E8+`WcZ-q_B$5v60DHw-gL^F55h3OmjN0Ls} `P=yXcsZY0YQKZ;WBR>(#vtefrO$YZTE z&^fD zl%kz!GN$??k>R;uv`y_sBI>+qICRYy>UiX31h!#&l|pn9HnH)P_A#_$ICFUTYfPOw zbbp@KYR4eUiK0qOEtXSMy9((q)Xd$29vwx=lg8=A4RUnV*^`2%0`m0c6doMq!@p)K z%*bF(0`W5@hC)?7+@4s@TN-xy9U9*!CUG>%q`p|4NiN T zX@~~P3;D_035Sy5(ys+b+>@GfMItE^%9=#Pn3c&&Iw#~Te@e;5tnS_s{4=2?`9=9m zp#0nSo00ZlqIXcNc=NLzf~S$Oa@*ytWG`f|VdOWx@_E?le0S-Q3~}cy1)9D&H}R}G zkPID3`R&0;2$DKcx_Z)z$44Vtz56p@XEbzVG&u7X@GC4a#&%ebrLhESt?vA4)Q8nd zhbsjrfsLYNEr`KMaos%6x0sihmywA$#OR`QduNySR*2;+tr=pj9{WOX9G54ET9!hf zo<3|PsPgrWvbIWBSp21mtQlyeJFg*+PvXR*V__d6=88om$mc~PuOhB)NK-nr&RF?u z5aaMn?Mo*wiw-}8$X!2SQ7n~dt7SFG$MX__k-Tx@tCVp`sN!l7*oJ9e;^8`KpwD|5 z2ZE -FuK~0 z_ 2W=6)1xgbGU*$; ztbLZPe;z;;I-X`~!TT!5!KAqHSCHJFeV_8^ldY|-u1Sprf@CHpvXPCDZc$6Ej{+&N zlzd?%G6I?OKP8y2N5IKaZdQ+wuAa$^U{{mcH&M#yWfl{#WWF=V^f6~5f+%YNk|IKm zMvf6Dv2vp>tiSUztm&OH6v$Z)g=Ovhix}x%3t8$#Xo@oziOGt$l0lxedF9JR7DZ+t zYt?GhcDIz5<>q=$APW6mZ{c8$d9{kaELJFFs81z#2aK1s)`7!2*^P2smeaZ-MoMWB z{mI2-E6W_UZ5M|0o?xdR84ghK$WHIw)1pqDJEu0}=cLZIwuVyGxQ5Jhe8poQgwa=} z7&dCiI>^rai2byj>H8w1P$G+jf{nwKTMkG(j)FN|K?`oJ8I4t`1PJ9vA}J92P3f|> zyv13UQ)K=P7Ie;;(>i1yl9h#JFTSEf`M~nAPVF3&D0~kr&`S{=o9`^yETm|&c9oo^ zxkINTZ|fiFksP>wx`FIstkauuQ-*pa0mWneKCyIiAC_HoY+2>tl|z 2CKiMsUbZawk!=ecf{ zHnj+Z7FhVho15b)jmVUH@X>**rY3c@$<`hy<;vH_Ruvo+<4quo6sV;~Iw_En0&Qe+ zKHym#j2P ej4pPxopfPgp)dFOzO?SjY~1g{eeWfYx(RC>bS0i>YGu) ze>N5Kw|b8aZLP3*ri@&$xYN6if-#B=oZ(uW#!^dKL-7t*ASv@1Cs(pp%1KPjoAjIf z_J{cXh{egmyo#WNGd+lx$cZ$d>EA {X%{<9hP4@5Ko<}8%fj_WPAB5 zTt0lst{VBXx^b~O3P6UmOqvMS#*IBvO$)Mi;*7aT*ia}JPB-e=t70cO<8>r&8@5vO zL1ssuNp8qY){S&Cj8x{9I72sl?@psW5?O9CvbFr*#J?}zxAJy~3POsg3Ugs%G(@>4 zC)<)~U6FAlO{D(-L!wTd5+BJa6mQBnW0GmQe-|DmvuvLqSBrIiemq 10Hk<^x1`pph5!^G^*m ?;I*I7DAab_7pC-2`rpt z4SwL8^IdfLJWUpAw9I(CZb7mgH!O8#kAfCdqGCGzI~9 Ox|T z69*IYNxQ)Mnnu)>V0m4*MkH%}os*|^wCih^ZT8=1PKgp9!6_9UMtwJl3{)P;@zr$Q zTb_>@1-lYlpZxOmaKB5iyNZt7jMe9PN-D(aI^ZZplO}Z}Fv`|2l_XhQUFZ-;v;(9m zlO{}=GGzg~KA77%kNBpU0&mTANA1Qh!{9O&iiO{02<>31zVSo)xzgpDLjL3~PN0p6 z$l+2*0MW%P#T5h;U|$Nqe-D `FO-y23tFIu!v zN8P(Kp>)o&7`pbAx{}qElO|2rM4dE7nkPdViTJA#1hC}ll}l`eH|Db<`!SuYzr^Wj zpPblQ>`J)lVcYd*KP2t#>%+mM@{)#(?oU$ZJCv-1m7rt^ofViteBkXExtqTbB3nSC zU*b>1sGU2fQGpuAV P&aGy8vS+YdwJJ8==(WWu=p4C#q7nkei=lcIFwju{CBQbSrcLI6KQA=-%7 zQdINO2K#@^ZTD0i$&h_^tr1UWtE`D!t{mjY2M~dj$MLX=lw4YSEXC(1O*5#ZH&RY~ zMb&6ItC;Jni-~roJY$fN66Ao{46MA&q?Jm3 Hbn_>dXgNtH0}m~u#?Y@u z6`#vTMmrM8Jh)3AD;e3{WwZ6`EG?@y`iHk%*4!AIQP#CatC@9@vWyq>~U45|}tZ;~`=({>aNfz_x+#0JhK^#1^sbSeVV^>kb=vVc_&(4$|3 z$)lDx!{V-FqU&4}V&|YfIB|Z)#kgo!zi!4(_QIt?H#3Y#)G>%HrgYAk(-QJ&5gEUT z)nhJm5+X$D$vLktxa;1Le@@A#rG1~KFrOho5JM8`9LUvUB9PZZSoE@)O0-8Um$h}* zMh^2`x~zF%Z?@{t>I)6im+UlRMC40Dh`dT}`(SehuA));=`j_r}^2AaoB*nLM-qpeFT zdP}x&v8&!m=8m`uZ^CZdOzFD|t*Ctjq>0fIr&iU%qtmgfv@5{e1X@#o0~bv4*5d(; z7g1*XYy9@XS!$U~zfhcroyfa%DDM9NZmljjE5G%O{8894klX9430SdQ1}Jo+bBw5E zmV!Xb;^VN>$1Q1Nkpzp9cUw%E(4llLpHf9IymI|JL-}RYMavysy {UFBY)g}$ah8b)24x8fOHNNWj&d6d%Ap%uQOR&se6l%7Ie zIg2^$RDwG7-|^IWt@g)Mn(dyZ$Atp#OBZ5jJ9H5G?bmbay{B#&LE~J%vC~og!kfJ- zb5lCA#cCX#DC|XY2`Wt)p(O9vT{34v=v_ujG#Z=g55xX*kFc&Ad*;<~ZTQCns(OYf z=1|JZ{Xxs{t4=>GurihQ{{TxU14?7;-L|Iw8J%>s7PDh3nc|6xL)U6Ca+9X$d@4@t z&D439nbjK7+p~$I6$;&J4?J3{1hyu_U?Q%%x?dI-w?YoF>HRlQTKTzI%apl-)k6Np z1JwaQo3@lEEorG)j@&s~VLUb?lC Opc=sRQ!mAph8q{`3d zy2}h7t>n`4`zx&sSsMpt<}JR!^c~x?DzWWYlV4Qh`c~gha!0F=7Wz6ykDBp22=yLi z?7FKAmMmd0(c|l^ygqzx7YSnGp-u{MF1wp@cIeULgk4*I;?b4-b=hX5+_6McOC2~w zMzkV|y?0MeyP}{7s2VDE8De%6#n3tw-JO(DNnUEx$7a56lTR1i2F|vH(57_Gh0v?t z^+oo}eZ@Ak?xKn0lPN1w$Fi*3ml^igGhF@V?0>gd?^TKZs^IcTIW$qG@3plc`<5O# zGIulYP gs8{*{ ZIjT!|{?np{9V7I%h!XoinC%3eWI| zuG{R$do*t&Llkx5w>ewAQoIvkp$-8b+ZmE_^^}vHg-g|5?cSVzIBXx1PR-xixm@Br zge}FsiXpMA4IO^2q2RJCL|r$EYH|HmO{0Lx h|B&4f2BvK!=v{3<&z8=0_&OW;0T39%j*cvEa#U%4zrj~UwzL6L{!+80bn zT?6)KQLB%rK7m8&LJo34S%h=8n#+)Jc4-2=Vl$L~X?|?K803ALss8|5=w(hmb6=B; ze_hJsVk`9tlXu#~dzHTz>xHVlJBIjW_4Q=lEG9o6ZsX&6ZoFvTy*k}Rm@CCl}3uW^B!ILs@^?1oleZbQxH2xM2V;bmN}C3&5=J34ge)hYb- z^VR&2i6(R|fRMTeQLLGMhh|KL59X{9(yx=A)v@pkHK;V^<-)Xs9OL^~R%h(2{i*)| z*7|7=2qrg@Po cM3Qu(nenUW7U9q_)+Y1v9A) zfMl1*MCtzk6eaN4yKl_>#K==vl^mSd9mJ{Gc%*{KI()W9q*8S(#|#rkG;Q{mXH?|y z*|ggXh OCPD+2>Jb;oWSh%8vP~k8*aB%@ zQfKb}0F)4YvkwDX?N&CDdUa@wmGIcWlO<8>yc3i~W~g`~H>WF~I`!lkB2JI-1iawS zhpnb18>rAInOS|!*_2_o^G3~1qsms}diJ83_c6*t;W96WGb@#fHD12dV(Y2@0Pi|= zL->A-aQ(KAoZmp9WDL0XzkVvwWinQ-xoJxnIx&(H>HMRC;_tzF52|Tnh%!A%S|ys! zOJVF>mR{RYGZEw=KPR?+%(yu_w-nQ#360Ieiq_R?% qhNDC1HYO7K^Ln?2cV7FmU+QApyL zgp~`HDwUnswTg;cndzuS3w3~lW(&4I_OI}p+LioOzJWvS3o7yLcvdSpk-t6LZo!qb z^BbacZlyI9gRed(B+?Z=q_Z1G)YRP%P~%+}P~%xm)ethsbw#IFvjs6tYH53IGM>lZ z892NoHes~`v3?ygkNAjooE?Gq^ZQ+5@n<3?nzVU4UT288#;I6(<@QM)MvKEGy%NM9 z{DYH?xMXP}+H`NmOMc_m+$uE^p%*zXNXI8&j6`)Mh2|RU+>IHuVPW@QvpVL2LshH` z7DMJ-D~ )O#R>9HaQC5a&d z`<*zjtWoS1l`92Yv$JZRQw#-P1jX_ )3b7D$m4OIIU2??DO%kN5V$`R{Ic4|H`^(5 zm$v&w?c-%eExs13GTW?;jv+a@5jrPQnwZf)Y+FS67%|o8l67hb FtT+MTlbA82^y3yE<8u-&5FI@OYCAL(t?_^ovsRh2Ngj{-_{_SOJ QvbSFemKmxXQj{ji_TA$*6cPz8C>(*| zu+qnG9SrPsWQV+O |}c=0&!K?O+H zB;=je*8ZI)1kr63ch**yBrdViZY*6rt5&0dx|-R@5wZmj=KS0t$MF`e9Y}{Ea>UO3 zmLnhLM=fe~rn`FU(&>pOS-zb3F&s|G+q3&e06Hj5yECZfc0f!@Ki&6(`2t3{QArsC z *-O$G5@5_t`!o^Lv&3nJEoTB7bNZOKgI@8qYA$ftuMrwCX}(Yf{TF zg-bRLC?c-*rY;i~nqhsY>sT}xdj|1f;}?#Nc+_suu^X;SnsQrP`ZTn-IvAZaNuy-I z$XiGqPgXkt)D(&dx?+=?+q5u7(mb{-OY$uO6$raKL>$|*I;uY8veC4p(@TT7iWQ#f zW`#*Cr(kcAAB{Sm2aVsk3lci(iLwrAG9(r0TEdgJkY#jC&RH&AZT2Tl{-XAD+hvRf zX2pxgM @xD>z4$-fdaJTLeG z9xfu8n{3UrDcQIbaq6sLz9OY& F0|NvD0000100IL95d{( a9 z@c-HX2mu2D0Y3rY))!vLv=jxs4K{VvAJu?8snp@xgvxRbs~xVGRr1(&QCH7T_Np9C zDz2TJp1|Q^t8nXe#;KT0q$)tsu+c|N8w-fsspBk=wPaGqZBUKJV4#is(QumA+Wpg` zB0jLC;OBZNsn58lyfwE(l5hPu BQ`=x`LKBG(UA)4=zu_pl#;}Y?~f!(^wE1dNxfrJS>ia>PZXD&kG~M%E+D= zi*_m~WSQ9*E>;)eI=43RX>|{TX`8FuN|V93d(HJycs;CbqH?bA^;{Acyl9MR+Gyly z`?RZnL;_Vb#y4d<70b`kRWuH#sR7;4YG}8sL%Op C9ZFtpUz8s2c zzAXx US293zT>{zIRAb7B=(*0u4@L!SH8QzdQYb0Tm<{*acB7 zMlV(P{{ZPz#ZN_+5$-Dx!p&Uaz}P3@RMgb&a2GaS8pK@ ~vb%KGD+dLTjg;AP+S>X7v_pne55lK_x!ImnaK2XF>aGvV(FJB#z26 z?y)91Lz$a}hf>Vvxy}{~ErKs=4a%&<;}PXyxhT{HAastn3(47=T&jvT#@x@{P!{rf zc!diK+#21=q`}}|yxNr7YBB!+jZ=A8MWYtFYr)xKuz8ACN8MF JDtx8`k@wpYOu nid;e$q(fiP1ai{w4IhEOFVKHOWg*5qJ_^*ZdQP&s$@++DoI@sm(mW+#KsF+)I=)vvn3LJ0Eu&gv>%W3#Lrdt4fNmv(aF1yHgvYI(l9- zULk|aRk-#k?=N-F2*Z1>Tu5~|HF8@YyHcTz+c;Sq(974deG@r>%{?VgCXEMhrl~Gw zUdhs?G4i`BRUR1FZ08+QWGEclpw<;t11F5J$@pAhmK~N*G*$JSv6Y*rNat=)Zew7u z`$q5zp^KX+YJ&=XUxoT>zEretZ)7CPp;c1lWz%5JbG=l>km#cpXb2l9>fGOQrmX`| zR8q1*cCFUlg1m1B(`2cxB&gUOy7HheEIw7%9atqIH}CSEBoE5ayu>nnH#+1eN*l6t ziO# TZ z-R3F|ZpfmXt6&6Os(DMSJ`r$k>P87|WH?k#BOjFMp5uF{YbqdiXxy-DZ_WKVJ>T+< zs{Bgvd^zyfAB59kb 5eoB^Z zQ!Z4sU^?z{!{%$*x)wVtFD4x9&nwHv@TH=S!>4sh_fyq1-vvb|dvgTuln);gH#QqD z?42SS?tph?d6Ncnl|Ai=s+4odbGs`mriPyDmbqv`PRfd&&Lo8sZNCbq Nu-$b zSLrd4W4fLb6osD%1#2sy7l4^+XS?!>bAv_Pt8x8u(~sp%RV?njg-=h;@2WUc*h{6= zVeklI)4FQ7Yi{J~_TJ4=RR^0M64PdwIbrpDqmkPtm9aqZ#i>nI8{r316-6ABvpOj^ z36b|JnA_Q1Qo#I$;9W^eQsZ(JD>BDxSy>2&bYcr5#>h$XqL(AV3LwcFm@Z!oVqB~Y z`B1nypOC7nL|1V?!gI^rnD7b~w^ts?&kq|}cp0|C@A6dAHYOLi=oNM^En|yWqd(;k ztWyvd-99olP_b83=XO!BQ&k3O-9cZioT=TOFnC!|R$J-46b}at7mX-GUMdIjog()v zs}5)VxI|c~X62?eys+yc8`$cMfEyJNYs!(Vw_fP5xAs_UxtW*YG+ZZWv54G2cvR)h zb;86W%TQH WbSXyeLN) MX-WDY&pypuM?DhL$*E`e&&yWfyr*b6v1cZPb)j zcS^5o**Omys3Okwu(0%aeYt2Dyit(A)*aOg!3C=*Sr$c?g-c7JAyu%L%8pq1V&>LV z4fFMZ jcZS*@B~}632AY!^RKGMt?j5by)&)s;2w|_{4FXih-jkx{8t~ z9h5M(HrOlkDh-shu0oN-s(Nu61SdSZ&5aL7__!ddINxW99* E&UGbbCMEP`sXZQ68E&JO6Qnj~&lDJb1d zGn#hvFkQh>w=FwD#v5wW`vo*1!)2NAJ4GHKKJbNnDojbE*c7wAQ+E{I)xs`RtS^uR z<`9~VuCKy4$Z&PjE97SSsWDg^PU&22Wr|`BuBvp$_(2W^zHgLgmHpNgMCJ}`o1hja zC7Kwp=<-LH@d>;vf@ML8* ob z#|azV4in3fw?;d~o4SRWMrAkm3Z%Sq0DGeqouVxxfK5eRPdH#x%wAPAWv#le=1oo* z&DA~=E?=ln!=Q{^$`-@+tR;ZW%|6!mSl5kWh?uDLKPsZWrmgv A6rsfSULM4zuZX$h@TbFOiXL;6 zwG{BkGdjeoVq~8(@4?Kn38<-M4GOBKInhMI_W+}T+op>vsWKh)S8E2k1&d>{%-=5; zO9%U?<8+3>U|t@G4t-&>e=5GG4Rj%~NG=uF@Uou8a@ )CmRJw?U{i0IZ*)d+)LVG?tweMl}sP@< P5g^yTYsnfS|zgVRoa&|jR zHq}YuAM`axSn7t}>o!5un)a1ZR}>7);;Jg0Q!|`>Zg*}CmPU&-uD<%GvRm{89CH2X zv4E^NYO<`U$1bYe(quX{RMy|7Jpv}^Cmm6J#!{d&HQ5fyky+&b07CjFusB$+(rV3% z8*qE74+)EAsdw<1fnlv}vMRq12#+$9{{RYj#Jw^jMB)9nkf)-l-1Q*?^X0asVVDF~ z%_#)@EJF&W$zUv0j4W|!LyvwLQ~871E}463n$?w9Efv$G0dvE)Dy8MtjCWOa2Erk* zSKt$r@|^m1PN=kD;2=YWap8BD^nKl5Z~ko&NwS)NCf<8dB8Qz+NK5vbW4eKXfEQ zQiIw`qjQAf#Z1m*h2-d~>0D=XnX)Y{(E3IxPEDLFnLRuGu1#Rw7l5Bt6L)fcBM{qF z`jeb&(4?r4GUTka5}j2nE{JN0CC`)LINb4JQr{`Ea-?;^Y1s^+wj6Eip_7$T$NUu2 zaG3tZIiCu$AuKLwPmd$W_~CUNuZ-QQO1UOFr#0<1AyhyNM4lm8KMIB3(`5V}QS3KZ zd{RW*k5!OOB<9;DVZ?istz=puCstPOp@>;>tQAHhM-GM8DfqigMmCMg6+odYPX-%s zmXPPuWZ1(6f(Rnu(_*j2%G_XXX<7%$Z?r2Q?G(n%DyhNKIFFx4bu5j2tfYt->~|~l z)_N8`ij%YHux77dvx-tffl`FHzT(m()RXl#j z5WiTiANd!W#A E9!>JlwB^C%7(9%A}+r*J_d% zxCG`ojTcol0w(@eXI|@_DDI2g 7 li?Sv$VgC^ (g;8aGdt zN4n)Q4KDL!?nq7 T3qV|g<;L7W@GLsy2!|y^>o&`#?4Z)o*(iZ`~>Q{ z7CP}jFY-{vXCBtHe+jtT&XUI057`PFD-;%ta^zL`bks8# LO|TXVFhHPGutzg&;ISMv&~>v`qTq~ya|+H>Zcv_JQRr-|-DZ^5+}U9?+_-{) zz1NB*&r}#YDq350IX;!IH*rp)XUdKyuvjT-9@A?tH6p<2d6Q>7mD7<3D5L4l^Jaxn zikoDo(0unR4Z&oq`jTkh`BE^(^26JNI7LI@bnelhT{Xq0Gj~;S{;PzEZPwLKOz9k| z
MGrCB><|`CC1ddrME TVY!(lJ^KCTaPQ&Fjp`yWJm^{;0o>0-+tUm&$rs{uFmNM+L5`Nq~F|8x-^097R z6pm|mDC%xim6KVR*eY0C3x6m`ivzY#Vs449S6P*G0(gr`drM7 =qGS9CBJ+mMdOolEd5m3?%;m+XWLxxyjNM6CEq^g^5-3U&@c#Kiy#1 z9@A70*eqWbZ5uK(+*Sot>WWLa+=X9?)6uu6>+rC8$*UUaU$yiL#Ba+&N6ollG~Ql` ztUH}b@hgL?K>RB!D=IeLRdfnt)0!SXD2pIf)zgZ_BFRu~-C0=$M%&4CK^HoxUtp`M zIZ%{ca+&g-{R|#mIcN _CoJ7E zVC9(tqk5IM*M-&5D;XV4D%=26&C1{1P|4-Gi+5P8*}!O7RjfawOiWX?$Z8Q-!OILR z{9RCE&5DS&O G4Gpsbr=*SuglrlG zj7%fV6AtODEt1Iava)Qm#&*#~D3pFsC=}Odo-BA@hA!`@8^X#cZm_&CX)N1z2*Vpa zyDH;(ife9Dzomv&XKyXrr<*KEyy`gRZ`+~tw74Ff)dmSH#6E2Y`;|=-AdE4B!D4u; zX`7Y0xk)ReIi0yq1+Qy@n77>Y8v};+US2b^my&g_aGOaieL$@F&RH~f%GbW>r8KD( z(BWApGGb5)hYyyi_F!0k4+J~vQ{i!Ps4R`DvB0|FFzSyJ>Z8MCsCBz6GXtijW@0Yo zJW!Eh)o5wlTe}Y7RNxXHsY-J+r(u EDzjNn`y6qQV8ys5FaH~V7hqPlvIanVL@EKwOsYp$dyG0xa1 z@Y=aHVNv*cx&5rEygN|A)ZmAQ-Tt)v0p>E?>TD*yoQ&e_9|^h?6xA(hu+*UG4`j_7 zcPZXxkgOvC&O_!O!fbI gVWD_ zKlH0`8i;zC)D?&}D0(Ap%H=G~Yit!%_6lh(RFh=XuMWq}CZvzqSz;Bjz`|TrM1}4y zB?jF^OEj7>gs3no=O1mqbl(j~;nw9BhG1{F-A97bH|$KLiG!pdz!r-zMplICg!$aD zDpot{$EV0rOix1-Da2#)n8jHjEy&WTT*w`o*HCzr#4XEHVm^9%)H=-gQ{wT`vn?>P zgTyjC+F+^bqnKT(u5j5lcXtRlIWg{y*9wxNI ` zn7+kOu?69;@B;f+i2ne*-V{II3ZG=uJuXg1fm5nq? l`G!=0KrddnBvC;E~4Eb zU|4(tj_(&GRp8K84{-|=z-lOtu3Pe;nb5lJD+ AP0+%;^pZb4ito 9 {v1 z#NBFQt4FA&&LS3!E~}gk&=+mcTq4`>p`&pqL?aeXMX8K?oEki;N>)J24ts`lhmfB3 zuwBZl5n($gVh%h6%vNTLFX|eN+@Y>vW{`u>`3O{aAz$=v0-ilhxIx5j)rmW)Na7V( z)ZIy{HkA}V+bY|uA+lu8XSAnOO#Vuzr7n){Uuxmv>WYQJK=L^KBU;*j_NA$wsrEvR ztrvMrrG@Ril$0O9r!=(KpzA0q$l1s``-C6zfwt{UoD*Z;Q!AOUT8d-c@`P?X+hk#B z1aoy&hjgA;cu%5p9lDMSDq33$xmMxjuGHQUgszD~6+?$Uc{H>xgK5~ =!ExVT@`kVlw*(j_L8XGo*K2OE*oa&Mr49lwMTr zQohTk12lxne=bb_03cHT0F34eqo~qscv^3`L@mP94=OEJ{B}ay$V%7*`hY#D#Jh5f zyaZetY@m=uh`hLZNK@%AC7dc5SktBaD*A|a5u2<<`f1!&8plZ>3!)l-yRct%6Lt!o zrXtV^>a@Q86Abgbh=8)5Ib(ID=dha%)Riti)zp*aJiXlI1GGF8!pqz%ryogC`@*c3 zOnQ8t#+64k#g?JvX!t?9sK{{Jv9AqIk H{LteabFQ zlm*sOKU-~;lu&Bir$E9HZH`613h4JVszcOh35*RE89bj0T#D8gMoX xOlzcemZ&iHvv+cgEci~QV$c)V9Ks4 Z+x*@fjb$UwhZAbcf26*{Rco)(Nr#t#r}4eD>L@88x*$-N!2;#cOli77p)G zY9T$uaJr>yIaEWZ))9EJV~96X)&M;s>RN|9+^0JEP(~QdXOe`AP$=eNbDywKz}5lQ zcnU05fsNAFjg;R|P{;eOtNx&xq!ut)s_4|0#bcOJxjU2^=WrBnG29&Ch`lw9Zwb+{ z+=Wq1qjAS}6#G;J^JUdmjH@##4}{usU1qAb2HB%>q=s2P0SrC9N^J~5eYAfH)=uF- zeK$m+sw!I(e=1tpr8gxPUzKjb?aU#vf;UM20K%uBk?r&imM06Kcn%9vy8D|qOIKO{ z04h3{4pZ#-$|JUg`e$iUN?cIil=#Wrys!Bv?4ym+I>A |mFTP#kb)J?yHRC=6j6KMYc zky28Yi;S5yqaizf*k7gKsI1|rCP(tMv>RnC%nuE5vis_2*=ChdF1b&t`8rTGLT0Z^ zlc4@8N{*ZCnr@Pr9S)>QrP$$_EWHK_jpfmkZvqj> d2bWF8f4 zf%Og}=PRJ(`0c-D5&m!#^!gq6OpKM#Rg#G(bApViW@|qxEI>8esNEFapohw k7JwtG*Vd}|m39Sv-WOA(k04Y?D(Dho+R<7tBra(%<9t76; zO>l%fIkzQM&Gs~IKfUVv-#D3ba;~1+9Pn1^sR``_Sf>puQB%3BZkFzTS5e8P>YkWP z>sdof97EEb>0%jMw#v3GOx5tP!MV)kQg10tIzT9f%GD$#r52(xjxzILjUL+-5vqaL zZ-n2dD)ebFf>mj$s&ST6n JYFn(?}QC-Fv-M9LrJQzB~zr4_zYn?_ZTP2(%sI&T4LP{Qe6 z8s934m{Y n zE^*i>XV$>+{6kK~7BS76rCZrXRP4{Jj)e!EH;T)W76Knqf=eiPhSneVJ0D+(TR1N}>g#0R6 zhD&`Y9>R`6G$)&s;O!>ds2cXR&nPXGbG;e|C1HO{zDr3{u=r8Z)6%lc00oUQHr2St z!A)^%t`j8>7YHkrEW`^0QAe=k5nL#n EWhGlAhmxvr*;^M~ z=XDe7t7GXv4w1^DfI|>x!X1Y&73>xAx(eVdD+`66-*>6s{we2dO}SXEHu(=uCDNRm zxGg7whNhI-mxb4Jg1RW9m !XOaH^)HVoq9Do+O**M)kMzfUBadNg=m0lYKPVc?{0NVO=e@QQC zd2>px#6IHf17y|)@spU>auk%`#Xc=OjEX&tjFi>@hz=X4!)4PctOFsC3ki_6O$$(8 zg(W*5vzv7`2}>se%8rq=u?7sTwJlj5&DTU~)KF 2$b%8Tir=H*dB+DF)g?I(m D12p!Gu<9`zL(eBJ zm|9PTXDusA(ySHL`z@7|wrYesR-ialaZx>nR(p!6s?`G+1%`L+vVwO)>xCXAN?USm zWmokLy)X~`r(*T4H`b=apHl};Pj`Z M$L|TXvAdGGra%U(g>4=aeO#g} zY=A*Cl^C-C%IM$5M60W-=XG>D9(D^2qYZ=A `{&h-UrAephcjoO$FKoz>wuli?&O4hzc zhbD^!inKI|L9tWF=^d%vx9yR-m_|e^sGM5spzTdoRtd~&g^FziLHdsB%(c_2;dH$q zf@s}GvjED@3tNIF;^<~Z$OX>I95$w21(3ks<;mDH#|~1^&mSRKbFv 7lS-BoE@T;l}F=_Hu3`Nc|CsfQ1%~aJv?$YgwVY>!o zt0Fro;e77W#Ycw40J+@cQ;I5TIHmBX!{%Z1?iFPdq5<9%SvZTUT{WXn2ZcO(A7P!l zD&M9)%1Cv{J`;dfM|ESCB_~>=KvC25;tdL}SFmXcB08XsGOo09sB_wbUNXVj`Q;H5KIY?MoL- )I*o$Tsi};v zw}fdREz&}$! 3+d6CG4Weg8_*+UCsZGw2!83>xVj{GW_T*&SfEbfx` zMf)kA{{Sese5odTKqXYxvPkXaK@^W6tsJUIj5O_G@D}K&K^wUX>}k*KPL7Tp&4R~j z`pyx;b_fZYEI40GQ0tt^?+9d3PEO%4?2ESDPdStQCp^gRoN8JrBZ}<{SyZ*%A#Vyg zxTN5tX9ZJ4=-qL`&`^w%Wzl!SZ8O}RhRG+DCJ8U?U(zazwCP6(=m2Fqrmz9FUKXv> zgJf=(s3K%O)l%cqac?S)k@HEY@PkJPQ^ovMG*PxT=^36BlQuSMaoJbnvOMXBV4p0H zH*s5_@~LqNpf_Pyc0U}xiFPPB6kOkwT+`(_JA~I+%4xOiI8Sq=cTP1+PS`?{OEjrz z+Wphvb2(3$k@8W+i+!l$4?Kk)Cm-$>RPJn>u-!)o%49(X1*H}&MhBZe!U@B56LmNR zHWv&1yDFM~gNsv~Zm`;MIXaP|fHKxy71O@~QEFpJcL~*Th4- l;lJxo%+QQ1^fe#Y{s;pw*L zlwP&mWnEjKXlE(foNiWxT8L23EH2c-u&h;;K8Nh&$ees8z~mtdq}427g;@_mTX;_4 zQa17g<6(7KF1ZO$D+Xqm%avVK{Vh$YEfxVXvE#y$S3K^arenp)Uuhb~YI<>K9#IG@ z<)-Il8~}2kC~m Q;vA6dwGl z `gRCjBs3R6k5=7HV?R zoDl3Cn7n?WA~`_aPEyU`syOwO1(Tx=H#tgf@~QpGd2}|zl_Lpo3O0ScIBM!SbzFqS zRDXH8RInB+Wg|5O1eVz3Rgeqm*V&E>Qo6dt=hY^W;Y>Q7RV!_}x^cPe9kRGh1Dbh6 zSfHs!#XcLqWB>xgGHfoJ6P%8i2jxjAak*6f;O~Va#E{%}x(H-%tCdj!i2Q_db9cB= zI^!#4fU?k+e4MVUD#T3us;Z(%0ct0e4$NBr07x4u+It;s;RdR6Vq4uMLtDaoi;dJ! z7jAe!SwinYX-x-!y SfVosLbZOevby-~< zGcM&-h(4jjplp~)QSsUl?R=r3R#rYoX$NG& UfD+iS7rub4zBbJ+} zDQ<4$=4s(sL^nWNB2`zq>PBC1sHzUI3Y-IVD}3bMRgqX#FIH_wfUmNz#aRH-H*%?} ze9zS5bi=+u)=|}4mDGvM1RehXN2<@2Sy641MbvCSSxF7!FIG(ntS5xpLVd}xv79M3 z+$Oj cnND>q%-8xvDy*J7cy-vaW3nuv*G?r#p2U z6Gf9F)v#4wk48~OTy{(vQ=FXfdz9*k$U>kr?44FhH81%_M;=xCbb_KKyA;@3J#Jk> z^Y~73qO%{Z!Btg0VQv$sgS@7}(r=@c9V6v;sY!*{dJ2*!Fxcg5AmQaXps5;Ipr~_P z2ZeBW%&M6+$huGXPfz6qNy@JO04d6g+EG(`GC!s`Q~v;sN9{x05XfZ3BAb*r$R{GB z_k~Tvvv&x8n=k*w07wu30RjRB2L%NM2Ll5I0RaF300ILM5E3CVK~V$~AR;nhaWFzs zfsrFKVxh4G6mr4QBtSz`f|B7hW1@qy@D)UJlkua%|Jncu0RaF8KLXn3vsQ#V2Modr z`E?XpfC{_#zKX5wW*#SGy`!%PR&g)sVy&&34r!d|w5uCp+_3mf5i_eWiF>Ve {7`JWrYK3 zyUd&QPl)GEUK|EeMAhRpSi^t_8iBRHcaq)`?CvXAM+zz<4+&@Hw^`_>SbY_$Mj&Z% ztl!ZJre_irSIOLpoG)blS1OU4VYuK|BCRUHMnyMU#M_Q)0AxFvBRm$Zwx03Mf>cE2 z;w?>0WQ3bQCwQ%Ndp-pwGzvZttl2SvobRLoPfeR!G}l+Pz0!kW15vj~+tmh0KCGK= zB}*^fH7>kxsU5imzI>H{3TEdJGFz}j^DBi|B&7C&tFeul ^H!G`Eqz_=DK;NOHZNDvFuLqjmc-3~UR0RU z0B+>5^Hp2CK(v63S6DD8YZ)$KOkMlL!qzy<8~JU|6HLzEDrbEa3LvXS+=a?$6kmIt zd7?u8#Hj^qfK$N?wh5+Yz@^<528`86ESyYbg7(5s ec`PlWm#Kf-De^D(P$Ti zH^uS`-#1`#*s_CJazmEHsmXC5_dcw$PH45Dskdrbt;Q0!uco#qv3Er>WSY^9 $Tdf+$e5ec{8I<{p8^YY=nB1F0qC=P%g(kIyxr`K?CHu3V zd2x_x)@gCIQ2ZfPg9^FkgMb62E;|-%8fCR*!t`2i1#(q}&aw846^KzPFI9!xRfn2! z R2?y$@ zYYu9xc7yQ+Cb5#0jtWFe_dw}QS99D^4&61cYSz}5DPE*#pT*b_voNf+RV^c`l+coQ zJ80yl+Cy_v*d-MrQ;++HZ`paZNz5#6B%mcA1cfdf=&biOjPW^C(HKebxk=GoET^?N zaU9--RgEXN915z(RB`T`3a 9xYi$T|Tw+7Z z6P16cmqSg#e`xOoCqWK((y+IEWBvzMR z&s!DJ1Lm9>HBgUqW7dHZC1`Mu4V%yD`l&IZ5XP?&>Q=8uhR4Ro8mczBA#sSw+P8%K zl-umiZC|u72X|w`A5~H S_!%V=yUGp=uvs`oH;6HbX)vu;e1EM!=LvX~7(x+Fq>5mPgf7EWsQ z%~mO}U__}BM29Iw(P!~C(2TTq$X~ipd|_G)9p}%zknHO_$Y!fN^cheznCzT6`!*i= z&!WxPm8sDmA&bU6DAC_L3#$66&v bGRrlT^6e!6ihWcMU8AX@aEE=qr~E2 z_2;rARaU;NOpVS}W0@QQ7AwEXCN<~egMh1_HGILrZkdk@mkghgRoKEiAUV-Bxb5bu zuNH;u+9Sg^vDf;mT>fyHW<61Z0iq1 ?}X zN>E{cQCi1)LiXEOaHA3oFn$14NRx*AoFrE0Df(`cWJuynZ-ul(g5qTnvO)_?&F*$* zvaD5S8X@TOka6BQ1u!bMQfTnC7& X%y2gQugRFIDL~PyFHNy6pg fXhoJe>$UnB{MtYvM< zUOKHz7<#{jgY!Y!R7#G;t+bU_MTQyi8%nJA7tL=ASKRb=H%Wx^SVBBrNX3NLF~g1% z_?tzj$p+{;EJ&_lw osMn+IN+ZN1DI^e3RlFO#t&!0}ss# z$M}k?V#-3`#qT4Z=#oD3ba$0V@S6~PL2Fr}wy?T(%l8=Zv9cKa)3j&~-5f_gNRo!1 zq&M0;dp4fn%|VIai7!=Z505<_4>7(}RzsaezD=1>Cxp++jU}?-w0iA=3S*&48O3Vj zfPNsS1@+tXQggSmD|B0uI?GAqt21;U9d1%mf3$DSS75D}Tb`RuP$CcJ&(T15xy+h| z$J$}s?npixq(Wp~7_70wFsFF$4AWVoZZ{AGs@lcM&_!$(`8&BF$W8Mpnog~y&|>jp zC9V6kp)vZmzxjwnhrHKXC}xbqBc#L-=a9uWMbY$n)djZ51y!?TS#T83v@}`5Yn@g& zXVq0?O2_d`l*%VHVIGK9b&FOna^&i3O^mDxblms*I>9og?f0P8K6B)st-jCP<1iYe z#p24uL+=(9uZ%xugl_SgB0z^K=Z0uSizxAjc--EBJ^m0%nZ-fo9rU6+@e=GgAmM1i z7i$YSV5J%zQ{q#G>f=0B^L9C@w%$nVKqT@->~&V)gK^9+XaRSNY0mJls0{GTrh;s3 z+Y_p{;dN6Lm@o@l9mE7goM0 soC LTZCq8q5|mL_uj;1P0*p9KuICYA8u-$mb{PEDwxeq| zzVk8`?8F9}2alg-VY$+4vsPI=4a&4KSXE6f9aG{9SqRr}BxcFa6(Z@5z%*|=Agf;9 zOFyF3@%-e5wbcQ`5{x)NG-5*4gF9EWGDfVrrtbqVs+D4FTDKHp1MK68Ez=^ZI{EB~ zZN11rWz9rG1lffyd$FQX1m$vcRs%C-`wn@ye9?QaCV HrGBPr*B2Qc0o!( zp?fC~>0N l+#`CgOdfU9vwHeHsvPK~o*a|MOf!*aG z%FgjQN%AvWmIXI%kD6^!x >67u&v{PhjKB3?h$d?!T5cf%TdQVf^0!-V zXKJw)tbJDNy;h~Loe$$U^iwV?Q{6Z7TFeK{RrF0TB`j@4ksDGwIDp5RvEEdMx@GRb zV7DNiq^h-rT$@OW2K%v09}N{dqi#l-G)$X<)s5l|ti-C%s}GUARfyUk{u*ryaH%*- ziFWOFMrvZ>rFxj8jMVir9Gn;WWB&lyt3_~Z)q-a-rsoo@Q+ZV|HicNuPOz%3xT467 zti$M4Spv~<{Z(qSJj%%cZI@?KBWpCN!`_B|OMv<&i>Ypx3%YIQ5PNQLn4? ygs;j{winT(pZA-9`xUJHf{H&yczUKE|?*9NqSeXHNMkjPw!I0SYY@akD zJ|&|8N93jk;rS=K6WGIm%iMhvVX#T85*rNB ZcvGa?Mq3 zy*4fi;I0VBE%Hj>^e7q58OJ4hlWkknhl|>s(vNEypyi6y=x~qYeSjk7GA5B6-ts<+ zd uVlFKM{X4kcKu1g;f;ThH`Zm2!} t4bunsRljQCZO7XqSt$ zJr^~atkWW`y)oU=WclQcLq%ahepgaRh^O7n0dNa7aH9UgT$BTowp@88L&EZ^iY=n` zX4Y!U6VJkXAH75#(@3|Ya73dTFTQ>M00_xyjN@qr=uE?|N^tcM2ZS=W)i*->OGjh1 zrcI3uO5R~wz`dKiij#z?I=F3Y?~uP_!-(0n*P{sCdro08UL_;h`B7@Ji a$JBugq))_>C2uY+mfiR%=L>dlMdN zAklf|xRmfxnzAE5xmP?V5T>0t zk1d^mm15Pz@}@| EGTDhyYl$<3_^_Z0* zV^vzgqY`jU(shNxx3eFbcU0zsXmOg-v`EHPPO=|NSKfuN%W9f1STAJa-iS6P`$E;% z0DM>~u!(SSw>4SzyDXD|8;0qpYW)?Wi&ceQq}#r#8>2s02nmeJf7y+3)M-teB73e* z(4pw9gBS|2B912}#hHlnb8#g*Nshq5TXsrvp3KG6Rc-2bsf7MyY)!~%k=ZEGgxWj} z{d)rrBSV<$!NoQdHq~ncgL2rgOq&`rTG*S>oq5WNnnZcT{#-Fx%A{y|L>c$p4$qR- z=+Rjk6Rvv<)SblJ4%V^0eUzw>qH1AYj7%BzRbC4Ky&23SW1SP^)WZ0$Hp~HYRd%{; zdi%>kFx_NU{%Ra}5o)^jy{lCkt+<<)x)x$o>Xr|hlqim`mJ54$X4NN&DTRF2vamAl zezZ+UsW@x}?~52XMQhO4mpC=zBP$zP6g@jd9HL9=nfI1eVDBdA`a44*ut*NTx{j-> z>6yVohY))vZ^RsjY;>n^vdDj}2khUo-zcBPiaVT#G)c7LO&@sC_z%5>gTgO_X2^{X zZrx$ReoB3nl+004qQ?DEi1+Z`0;?%CSDdouwT~W2%$TTKGSZz4&DmIlX1j7%PlX*> zFRR@*gf*tKhIk_*V7 F4oN9#FZSI8VjQ7zuAob757d-QGjw&9F2F+`#wZE!n zmNbDCvy-fpiVwIDn%@Iz?VAx{xdPMJLw `OJPgeKv6D|zK!m|Uh#3iU8l08HgN zO46H%j1SATggi5K4bi8&qQn0Hk+<|sgFEdi9+3pe@ZC0)$T6D6Lt&hm-YOjOnN4?j z){)pnh|U?+9Xl9j ltixD?uJB^-zEk3T^P<2IGy7vDSTEi!fe2G z=cN$Ij@WRG{vw;Iurqc_Z!%l6DsLbJY7 nJcsGKCSXMx169;tWs>1}$H}4gO z=oL;Bn6V9m=Au0Cw&01K=9Y(SL+MC~RMmCGM$wP)Vb9EVjq0m^w?>}lU#?E_p|Hi* zyO2SngkMDQ+AV4JvA0|L(J|OV9w)9!+o-TEC8^v5aU#b*&?(mQawA7zYk!#YdIltC zJ+sO{sk*?fk}Pl!VZj$@+vCD-RU$Nu!k&*+ztc5WNMk<}M3t7P^Jer%*i~5CK_kN6 z6>^fQ&`IPISr0;vLq;M=#riwg)19hyrnOM;ysi6I>7vo*6pr&&zd~pq1|y=@3;Wc= z(S`zLM(eQ7w3QF=)2WQW;|3nGc{xMUn|wD
%yG zvV!+I9L_kRORG-hzVj@ksxi;=HztW$HC5~m1x^&6%#Gm&=?tk`ye(5Nv!aw(Eq60| zh*;T}uJ3n3)xvG%V>MJg!E;2m3&JEc{RpuOYA3|0kH(f-elN%$r8UwivS;qkkS7c>0r z*N+r8^IEoo$PKx^F1?UMcm-N)xnFG)eWgy3Ds{4%!gx_)49F S=ozD8LQ}~;A zF+oKgurdOP_+hxB4WOsNBXHXc<`q=k8%=zlb0VJiBiNlEM|9)6{{ZH%Qfy**8&yb} zGa?Q~OMgR*Iye>npGf&F%?c^%@V%Ca37?6?s1W1DZmH&}V_6}yUy38FV^NJA)`cV}DpSo|tr>=;on zGiuT$cC%)93R*KL(9z;y6uGB`$?% LXr^P VsO)2EmD=2|E^GdLs9Wutn~??eIBS z59X|pR>XIzyxOg`#`QBu8QPr{$|P4FClZ?m9pyF?aQXIx5qhg>fPdnvG|U}|_=d%b zLY8%=3||Yx`co`$QgD;oiY5d-U e^|u`QA6DWAf$i*1JKt?PNCq-b`Y^gq#6a3G8g6 De*+{ew$DS`$y|dXM48a&yu@pY9l@sBSoAh?Y+@8IZfXnx6Ic7Tp>+`TZItx zaBN_#Q(2w-f(;iA+?A_wu-!}cI&3)nI}tQTAB~@#A<5Ap+6|9k?*;6iZ-Cw?_RyIV zsJGRBrTMRA35@6wg%zRADz=L85xE;2q;zPX+P&RDo7DK<;-h*C5A?0rOdT88Xwh?N zZ4wqRaXTa-ut}{}E6TM`Vkh7~MK1T&ioTmRkQVWplSNkIqy-g}+-;(;ydu|)$Boxy zqQ$_v#B_%XVqMes3ZYo4yNFOz+kv!GH8D&=HX+Ao;5PKd2Q hq2>eQHdtK1eV~VmXkaSW@oo&6QsxY|B5qMK3EBO~84jV&SFdH>?A|Dp$qvHC} zTg9>hZA{Q`#){p>+`Q>`Bw#k@J9naM8FNI3WOhxcxZYDCH;LAi4Gv+6Yi%t}iv_#z zer-jeq@GI1a_WhVq*V>Up?k9s{BB#YA&==&)wj`AgvPLuY=t?|swa94o>)*@ny)Kb znjOM>!ARV0Ing%E9!qyfclBDgn8$pU^b$H1TB#73#7;cFbS~I#jysmStrJzpm_TDa zn)#+N7%sx)scq) @MDPt4hM85gg>9w;?@% zMd1f}?GA|a*ph1{Av*C6c?4eURJLnG6 @#y1*`I!*0v)RU5+E9psr77@36W%Qg3i?W-ZU-_1pE7RrMbUh?VWo1*Z~ zdHoiT38VOc`Xc34d@08(HcLEPP3WQgOkAn9;j=Sf=COA`rp* oH7;5u- 9eA?V&8_z{DW>92fZVuVsaB7qm%i=0z%=Q-E{{W&c z#8~PFG%5+rN$epQZ;=ibf~%Lg?ZKr;D)k)T_DQuuI#!U`Jc%{F5R8u|^C-G|$7!8* zN()AR;t8#N#Y!E-U6QuM8$`{F%{T~#qj4y+MP+aI9r-D}(>(U>MU087f;gUkBpn6@ zvMBy6OI`ju2@t0Z`jBt3VHm7 l&lEN(io!=_B32F zQ@~xcJ{xvT8a;d*P;jt>71UMki^`KE45T@8Mievbhy3WhUbvSMy waBnKZt#oNvMZzo!vLh) z8TDL6aTUZ@8C+#mVVPE1P3B6_&8Tx=cJ@6K)p*Wq7JOH!L3^GvB1-kEfHh+iiZH{D z{pXbzp$-#Eoa=x=`fiaa_aXkOXGdw5dLyQ{G+h{j@E~2G!<6lnidsA3wb(Q|veq(0 z11@SVn;E1Mb!?x{XR4iDSn)sfRijQbzAL>prY{Ps@A{)&5NrPcn9B;*Iu5@^{%bJm zFka!_tNlB_xmlu*H?7zZhpArqto$rq(mZGNa=B{&>qVg*Lkvk;WTfC)ay084-z?B4 zHBj8^l#gTeDyw)$&V$OI*>s4|$As?%?BcP`$k?KN;D$q%=B@LZxI5&FKJ6krZ&eI> zp!Zo~ssSZ$8aH7){{Tw&1a(`_V^;iU9)Wwh-AdeJM?tkzs*!x0p+jJv`k9lWs?i)y zLtu=orf1$ei2g_l_GF-e?#Y?vszQ^RPYaXT5CIXnDzg=wY#Gf43z#IJ!Wb}YR-H(B z%F?FBZ+MOUjMngyEpP-I!ADNe8T&u+A&=qn9H{24u~-#>Uf)G033Ns;{-Et1C21H{ zN6Ah+c=7z|g;Pa+M)+!}J_92URllKzo`f%D1&!w=HN3!RsKLD4P10V|+rOR4M@54# z!i;SWV7Ai_^x3ggW#3v)U8~hiCr5`7r^M7tbRfcf4 Tfh(^+fF%R|aDmvqFqC`v;tkJh%MLWL<$yJn6w9b GA@)QZD#x@+38(AMkWzmZ< XBJ&7@i(o6rZD(gT~Tsc41l-#4Xs)mQG&Y4EeQL7&PSN5+o)x zmOZX xC1Cgre3 z+=vBMGVLuZdFrbjGe83r$St^WApjlFXR;9#HQ5y<&DNSCKxyBrx|k&*+X{S+`xGdk z@w60L;V^Fs32yucMHcvZ9h=pP(yKk-btpp6GM5~Xu-zf2lb00wuWH$I3vuxL-6iiBIxlCXLGzeS5bHgqvi5GQ7v0{?z-Lt1Y8v;F zTvHt2cLbOxV?t|8D$N+qN*fRG=B6#=rxAu&WwdFqtNTs(tQ|_+b#8%fwp~<&Ne)YR zlhrm<2V{xQY@ZdhvzHPy2O{`|(4rg5M(g?VRUx>%V&X4RKF@jHfOmPfZqm1%Zb4Mh z;g(kAHy )aQ(Xu*pifA#cL; zUFMZnND$%=jft@|eK5dy5)C`dLLh5i(fmdl%|-}oK{vQ7QN}B*{2>^Qa10omlcVTy zSr9Pq79Eq;n
F%0q%s)D3s4ZW zrxzY7$T9<(_wzf&Z!n2pFsd<=Xk0Q@tCfnXYv0E6V{#i|-XEnUhlVHDVyi$h#H*S| zqHdPhrW8htpTO_xADRxLw{^<;Cih8H(b(n#%5EyYkZMl+TvVXL1D-vMM);ak2)LFC z-Bysp-h_Ue6WNOoCz&df;jvt=lD%<-tFGGeom^;}XXKq5M`WUlUkAm}R-L6)4yxPU z?+8tFXhN*Ln;nN GRf1~ObrT|TxN}a@^utmMH=J`^M1f~Ou2)mK$#W^wk*I})$SX$G zw;K^-uN2$F+vt;NBgq!K+(b5y4>d61B8zWQagz2zXFLMeMsW=BpUG5!z}|SC$ ExpQs3Jg)^AW+ab@RmXO#4$v4s0k@NMm1^ z;aV@99|PC%vLgbIAkEH&`->8~*_E-_2He0A`Fm9l{~M zbxT(N07Ya7pW3AMmY!p3)`fJI LA@@O&TZ1!|J=PZU+LgqNf}@jtE1wq}@A|!31L@*ToWq z1%t$ZXaEa<+Pz<}`C3JxI tpqUp zI2(=UncJCF8h07 50@TlJ z*3UA9M*=xg`z&)qyIpx3gjJetw*_hrb7s+ktQbMVv#V)20Joav^&1i {qmi_xzUZ26@`ILq|Zb*uITa6V=pL zeXl$W@h3-&>d$u6$yH(5&tgETns$#5Ndvq$T!7LlL5Y-MFPg5&Ny5UkwzdP_*2Uf} z95Vr5 HuB?(C5_ znjIQDz{$9|MA9O0M0C{q{{Z%^Xn!IW M5*?F+xjNx`ZFuUdAlyFRS$kE{p?Q{E|hCxkHXy1bEOkbcLNR3 zMrWTS__3p%WJ5WdeOBx(V9l1T_)c$7rbOlPO_5fxd04kzM^LzHeiYb^vCK0FJUaes zh%42FUq=vDy4-@Q-Nt;%y%uQx)+hWzF=ITr$7-mx%XsXB@5E?l*^zW_ya-M9WsZ~O zmA9v9x+EvW5#a{u8Z<$xh|=4gZT2r|p}rG{oXAed+9(=4KjP^VapVX2OoZMRHbFWo z?jJPhj=jcZ7Q=g*;b@UoX;1UDf2lr!5Olb*{7P?goOzX4@!&Gph&gQ730BB;C_V0+ zvJ1WFxR0W-;a0#RPA9W*$&@tG$PgG`5#~(+RT6%X@$F*g$a12*L2dzRp%%MJm6N(3 zGtE0l(o39QZt=A9Dzj$he8Rm+;5c(rgzASZ6zb2LRGcZc-m7# JT&l1a9!q@7kX$~y5{U_p z>eVT4m4Sgq`CqCihDwXnyscl>suJK>^hO^2jLF(AW(J^N%+I#I27vjo+p zJN@IbK{BybYll*&FiuN#zH5FbqVFFy!}F$hHu6