From ca3046e8de32f7f7440fec272a33b694ee2c42bb Mon Sep 17 00:00:00 2001 From: rpm-mcarman <65228808+rpm-mcarman@users.noreply.github.com> Date: Tue, 3 Oct 2023 12:24:16 -0400 Subject: [PATCH] Initial Commit --- .dockerignore | 8 + .gitignore | 4 + .npmrc | 1 + .wappler/project.json | 38 + .wappler/thumb.png | Bin 0 -> 4521 bytes app/config/routes.json | 10 + app/sockets/connect.json | 3 + app/sockets/disconnect.json | 3 + index.js | 3 + lib/auth/database.js | 72 + lib/auth/provider.js | 149 + lib/auth/single.js | 26 + lib/auth/static.js | 31 + lib/core/app.js | 713 +++ lib/core/async.js | 97 + lib/core/basicauth.js | 43 + lib/core/db.js | 210 + lib/core/diacritics.js | 96 + lib/core/memoryStore.js | 92 + lib/core/middleware.js | 151 + lib/core/parser.js | 752 +++ lib/core/path.js | 97 + lib/core/scope.js | 63 + lib/core/util.js | 185 + lib/core/webhook.js | 24 + lib/formatters/collections.js | 169 + lib/formatters/conditional.js | 30 + lib/formatters/core.js | 27 + lib/formatters/crypto.js | 99 + lib/formatters/date.js | 83 + lib/formatters/index.js | 17 + lib/formatters/number.js | 73 + lib/formatters/string.js | 210 + lib/locale/en-US.js | 59 + lib/modules/api.js | 117 + lib/modules/arraylist.js | 173 + lib/modules/auth.js | 53 + lib/modules/collections.js | 185 + lib/modules/core.js | 269 + lib/modules/crypto.js | 31 + lib/modules/dataset.js | 24 + lib/modules/dbconnector.js | 765 +++ lib/modules/dbupdater.js | 412 ++ lib/modules/export.js | 109 + lib/modules/fs.js | 258 + lib/modules/image.js | 505 ++ lib/modules/import.js | 129 + lib/modules/jwt.js | 22 + lib/modules/mail.js | 92 + lib/modules/metadata.js | 484 ++ lib/modules/oauth.js | 24 + lib/modules/recaptcha.js | 47 + lib/modules/redis.js | 237 + lib/modules/s3.js | 174 + lib/modules/sockets.js | 172 + lib/modules/stripe.js | 4993 +++++++++++++++++ lib/modules/upload.js | 118 + lib/modules/validator.js | 18 + lib/modules/zip.js | 114 + lib/oauth/index.js | 170 + lib/oauth/services.js | 98 + lib/server.js | 94 + lib/setup/config.js | 107 + lib/setup/cron.js | 46 + lib/setup/database.js | 36 + lib/setup/redis.js | 8 + lib/setup/routes.js | 249 + lib/setup/session.js | 87 + lib/setup/sockets.js | 198 + lib/setup/upload.js | 61 + lib/setup/util.js | 12 + lib/setup/webhooks.js | 22 + lib/validator/core.js | 243 + lib/validator/db.js | 21 + lib/validator/index.js | 120 + lib/validator/unicode.js | 466 ++ lib/validator/upload.js | 82 + lib/webhooks/stripe.js | 22 + package.json | 51 + public/bootstrap/5/css/bootstrap.min.css | 7 + public/bootstrap/5/js/bootstrap.bundle.min.js | 7 + public/css/style.css | 0 public/dmxAppConnect/config.js | 8 + public/dmxAppConnect/dmxAppConnect.js | 8 + public/dmxAppConnect/dmxRouting/dmxRouting.js | 8 + views/index.ejs | 23 + views/layouts/main.ejs | 21 + 87 files changed, 15438 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 .npmrc create mode 100644 .wappler/project.json create mode 100644 .wappler/thumb.png create mode 100644 app/config/routes.json create mode 100644 app/sockets/connect.json create mode 100644 app/sockets/disconnect.json create mode 100644 index.js create mode 100644 lib/auth/database.js create mode 100644 lib/auth/provider.js create mode 100644 lib/auth/single.js create mode 100644 lib/auth/static.js create mode 100644 lib/core/app.js create mode 100644 lib/core/async.js create mode 100644 lib/core/basicauth.js create mode 100644 lib/core/db.js create mode 100644 lib/core/diacritics.js create mode 100644 lib/core/memoryStore.js create mode 100644 lib/core/middleware.js create mode 100644 lib/core/parser.js create mode 100644 lib/core/path.js create mode 100644 lib/core/scope.js create mode 100644 lib/core/util.js create mode 100644 lib/core/webhook.js create mode 100644 lib/formatters/collections.js create mode 100644 lib/formatters/conditional.js create mode 100644 lib/formatters/core.js create mode 100644 lib/formatters/crypto.js create mode 100644 lib/formatters/date.js create mode 100644 lib/formatters/index.js create mode 100644 lib/formatters/number.js create mode 100644 lib/formatters/string.js create mode 100644 lib/locale/en-US.js create mode 100644 lib/modules/api.js create mode 100644 lib/modules/arraylist.js create mode 100644 lib/modules/auth.js create mode 100644 lib/modules/collections.js create mode 100644 lib/modules/core.js create mode 100644 lib/modules/crypto.js create mode 100644 lib/modules/dataset.js create mode 100644 lib/modules/dbconnector.js create mode 100644 lib/modules/dbupdater.js create mode 100644 lib/modules/export.js create mode 100644 lib/modules/fs.js create mode 100644 lib/modules/image.js create mode 100644 lib/modules/import.js create mode 100644 lib/modules/jwt.js create mode 100644 lib/modules/mail.js create mode 100644 lib/modules/metadata.js create mode 100644 lib/modules/oauth.js create mode 100644 lib/modules/recaptcha.js create mode 100644 lib/modules/redis.js create mode 100644 lib/modules/s3.js create mode 100644 lib/modules/sockets.js create mode 100644 lib/modules/stripe.js create mode 100644 lib/modules/upload.js create mode 100644 lib/modules/validator.js create mode 100644 lib/modules/zip.js create mode 100644 lib/oauth/index.js create mode 100644 lib/oauth/services.js create mode 100644 lib/server.js create mode 100644 lib/setup/config.js create mode 100644 lib/setup/cron.js create mode 100644 lib/setup/database.js create mode 100644 lib/setup/redis.js create mode 100644 lib/setup/routes.js create mode 100644 lib/setup/session.js create mode 100644 lib/setup/sockets.js create mode 100644 lib/setup/upload.js create mode 100644 lib/setup/util.js create mode 100644 lib/setup/webhooks.js create mode 100644 lib/validator/core.js create mode 100644 lib/validator/db.js create mode 100644 lib/validator/index.js create mode 100644 lib/validator/unicode.js create mode 100644 lib/validator/upload.js create mode 100644 lib/webhooks/stripe.js create mode 100644 package.json create mode 100644 public/bootstrap/5/css/bootstrap.min.css create mode 100644 public/bootstrap/5/js/bootstrap.bundle.min.js create mode 100644 public/css/style.css create mode 100644 public/dmxAppConnect/config.js create mode 100644 public/dmxAppConnect/dmxAppConnect.js create mode 100644 public/dmxAppConnect/dmxRouting/dmxRouting.js create mode 100644 views/index.ejs create mode 100644 views/layouts/main.ejs diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..74c46d0 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,8 @@ +# add git-ignore syntax here of things you don't want copied into docker image + +.git +.svn +.wappler +*Dockerfile* +*docker-compose* +node_modules diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..277b791 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules +.svn +.env +**/.DS_Store \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..a21347f --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +scripts-prepend-node-path=true \ No newline at end of file diff --git a/.wappler/project.json b/.wappler/project.json new file mode 100644 index 0000000..e7fc51e --- /dev/null +++ b/.wappler/project.json @@ -0,0 +1,38 @@ +{ + "projectName": "RPM_Access", + "styleFile": "/css/style.css", + "assetsFolder": "/assets", + "designFramework": "bootstrap5", + "frameworks": [ + { + "name": "fontawesome_5", + "type": "cdn" + }, + { + "name": "bootstrap5", + "type": "local" + }, + { + "name": "appConnect", + "type": "local" + } + ], + "hostingType": "wappler", + "projectServerModel": "node", + "runtime": "capacitor", + "webRootFolder": "/public", + "useRouting": true, + "addBase": true, + "routingHandler": "node", + "projectLinksType": "site", + "targets": [ + { + "name": "Development", + "remoteURL": "http://localhost:3000/", + "access": "local", + "targetType": "wappler" + } + ], + "activeTarget": "Development", + "projectType": "web" +} \ No newline at end of file diff --git a/.wappler/thumb.png b/.wappler/thumb.png new file mode 100644 index 0000000000000000000000000000000000000000..287b22d17e183178f05bbde887719c6a018e36bc GIT binary patch literal 4521 zcmeHL`8(8I|35SL(9C2V^JueGxa|~`2xC(AJz2(HqFeTRiW$oXNghHYQrRk!+pesU zDT*jWx4~FL7&K!U+gQHS_j_I6f8hD)xvu+%bDeXp^M1e2>v}J*vm{tq81kbJqX7W? z#zqD<0Km9I7!JwH?Q9jE{aS#9*cjqLS(n%xcY^TNH`fQCGG+g^2M>3S3N&&I0YIST zSA(?$yu1p4u&%Lz{-sdY#q51)`8A>!$M*J4)X<)0HFcdW3+Xybrkx$vTC`E_H^TLO z{-!>5*6y$D7y8QMqp?Da(LMfSm_)b`>LTne;yEygu?EN9$$|UsSjb2XjgvmeBZW!& zXZgRzE#qPv^X1KV^!BzxHYQW_1mc<9xm+Y8tf@51oU?bbeka?| zmvRq)XxzgJVsl+hk4x0v&br_+O)>L`O&k7u9E91cd9j@%ia{KWVKFf1Ut&bKNcuRNV zp8pqM4O~g#UmqniS>v&S0)jfLJ>PylnZ_2$A;G+lqF6!Wc$3;! z@bUx(E$oVpbK@it$yuyDZ&i)9Ctl; zLPw$cGHszh&siv8Ue0^kplf06`SJf}O%4)9NEmM|yy`yf{q&3ZdAt9GCcvW>(_qA+ zxsn&nSVwOka0?F#KCIB$Z3=D}dOmosd(YJIZO!hfB>HPF!oX#iGaouHMkR8d9*!JO zw|A}YkpS})KE2(INsnvaJ5`&*GYcG|Xb??lCW2QZYKKjGu3KoOTW;~AK_%lv>eeqO zC6h-aNLH&EKKt8N25N4|L;bq!HAP__EB)nEBcjVWYpL+pl}3FVDzC4) zrl6j?$)>qUQf@gPD0l4Qch9(WulpQ^ZO$BxXzEzRp|}`wb3i$WUv>cIY>Z4c~{`q_*VDA=js&R`^9AC4}@XRi% zDG|mc#EvrY4?;Evy>rEfw1^Uvfojk%3QqyD?nWgAuokUCG(-THndp5In$Dk)4wyv5 z^iKbp((fqY!@a&T0wG5n3fr>4%e2##0vzih^*c3{S?h;9X7<~cHjIYNWVeuX`jJq? zNrhn)+t(2Kak`e>HvB>xM1>eg5!xuDzNFtOvy(pX$!s@n7t ztvAo3$rb?+QFGtBWt{lu)l&zL2<3NGrXTg8ZmB`iNzc#l`>R92Ng$Pf zY@>wsoHe+LPn{bE{t9UK7gBKEdl|gDMR2G(+DW&WU6xqy}c;SEa4K zA@)@RZL*)E0?>^ewP_M4kk-3`la0#d5*0YS2e)#W?Z@Ob`3J~LV?XIr&8;yNcKf`F zKysXx@!_F1ySJxaeyVg(gzEhW75+>ID@mPn*9fIt5x^OG^2B{v)td&F5+XGt4Lq#o zyR?~N{#@E?3AyoekDOADd|jWQG@}erX0aG(Yc^Z$<;QmIw!AYd_pTl*A}ume5Y03S z8PtmQESYW(D|<^?GkAdvJGnMCR^rw0-u)vQgrcY?u(- z)5iscsw+xRQW~=XC8j5Z+7aiD&r2U)yuq_~$x#v1OopU1QL0oHe^fdbTS)k>kuKT7 z$1{>P1xt5k!b5)6DQjMKDB{v?-tZgDr6#=8NuLWbRl3owcKKx~c-;EWxa*ywaCh;c zRy&ID%9XSxI}2g(e6MYI?>7#l5voD^D{ZpzA`nZwtQsHk@i}45<5L=XGadW@qlWsCr{mTMu$nj!nrrCOG&y6xMR~YXV0|HTq>UE0bKWM&TdhB+P5hSFNA*md0jFD~z+rb!2{2~1 zL&D8>C#?WK@)+;qm#+j|(Y7805l1f8PQqB5YTQkhW^%6T3>kK25c!_z0yGuQrRFI)h8%U|i&jiDtNFEX7P%(Vi~asLAa760igOLu|^ zRlI&x&2N9s|AG;TlHGklbU}okj&Iu5CMvB-0mk1{D`0HB!+z&fDy;3iJj;f*;jX_ob$(-+TRmtx#GJHi2;zjVKOft6Ql2ASB0}OeP zdx26L(?si=)psc!)5u>i2PNm9bY$HdJx(COhw$rOSqV!|Dg=AHVrHl2~Q~w3J{fu4R*FRn!0@K zYlbozJSfT+Z_%Z^EY*@DV*yL`yP`-f>D8J0KwW8mGS}iZs524zmBf_WX899uE6WP8 zZ}H9yUwuN2DDZo1s>>MfJZ0-QRt)mOMTsXRdN}-WnZOJ#15mHzXZoM`?N;ggo3;tI z=;#l8IEg(ruEL44>f9dA!H>HF~(ua~`+8yEtqmm*+#yXkff2Ro6Z=JC9Z%nGX z1q1XYiAAquoG-`5km;QN6v(3Og(IbJOCO*6L4mlW4-%+S|H`Gk309VuKK!Y>)MUps z20(su)nMB5LzH#cFj0;p2Vcyne{4qNwDpL>%TK+aR^@;^ z=lTserYX6I9^Ig}<#P4I@s9q7(MEXQBZUa6>fCDtb@>VpFuIqkKsXMDoB|9d&eh6# z{B5-_?b*TFiN=2RgZG+3?1HNOu)u(c?5avELS_h(~Nb^#Ev?m zw(4<5S{j_K>ljN>`>oD!Z|XHu9dU?*?VdgN7m==08Wy~}D(^kT`@N3v%55esRpznG z@wY$fnxjCN+Gx;RZ$V4tgq!QD@O$83p7dAY!)|UWZI5gEiW${$)34J{_pMb)E{JeG zZn_4$3h^D&hZ|*Ub%c43`5dT4e`3Ql#Eq7vse$BqY5AEc6T)?MKUI3A5!MRw3$~H6k*!k={gt(AouSE!S7R) zWrAwZw;PnVBh29Gi^hLY@}zs}upa5HlC-g{@D4HdP2VBcS2fC3{W{gkr8^32Ys%m~ vux5g?iM8+g?Vq>lKTr1m^sB<*&P2eE`ew|;ltL)?dj%L@urMgYyT|+wozi@m literal 0 HcmV?d00001 diff --git a/app/config/routes.json b/app/config/routes.json new file mode 100644 index 0000000..215bf5f --- /dev/null +++ b/app/config/routes.json @@ -0,0 +1,10 @@ +{ + "routes": [ + { + "path": "/", + "page": "index", + "routeType": "page", + "layout": "main" + } + ] +} \ No newline at end of file diff --git a/app/sockets/connect.json b/app/sockets/connect.json new file mode 100644 index 0000000..18cca0a --- /dev/null +++ b/app/sockets/connect.json @@ -0,0 +1,3 @@ +{ + "exec": {} +} \ No newline at end of file diff --git a/app/sockets/disconnect.json b/app/sockets/disconnect.json new file mode 100644 index 0000000..18cca0a --- /dev/null +++ b/app/sockets/disconnect.json @@ -0,0 +1,3 @@ +{ + "exec": {} +} \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000..e3c0683 --- /dev/null +++ b/index.js @@ -0,0 +1,3 @@ +const server = require('./lib/server'); + +server.start(); \ No newline at end of file diff --git a/lib/auth/database.js b/lib/auth/database.js new file mode 100644 index 0000000..5492455 --- /dev/null +++ b/lib/auth/database.js @@ -0,0 +1,72 @@ +const AuthProvider = require('./provider'); + +class DatabaseProvider extends AuthProvider { + + constructor(app, opts, name) { + super(app, opts, name); + this.users = opts.users; + this.perms = opts.permissions; + this.db = app.getDbConnection(opts.connection); + } + + async validate(username, password, passwordVerify) { + if (!username) return false; + if (!password) password = ''; + + let results = await this.db + .select(this.users.identity, this.users.username, this.users.password) + .from(this.users.table) + .where(this.users.username, username); + + for (let result of results) { + if (result[this.users.username] == username) { + if (passwordVerify && result[this.users.password].startsWith('$')) { + const argon2 = require('argon2'); + const valid = await argon2.verify(result[this.users.password], password); + return valid ? result[this.users.identity] : false; + } else if (result[this.users.password] == password) { + return result[this.users.identity]; + } + } + } + + return false; + } + + async permissions(identity, permissions) { + for (let permission of permissions) { + if (!this.perms[permission]) return false; + + let perm = this.perms[permission]; + let table = perm.table || this.users.table; + let ident = perm.identity || this.users.identity; + + let results = await this.db + .select(ident) + .from(table) + .where(ident, identity) + .where(function() { + for (let condition of perm.conditions) { + if (condition.operator == 'in') { + this.whereIn(condition.column, condition.value); + } else if (condition.operator == 'not in') { + this.whereNotIn(condition.column, condition.value); + } else if (condition.operator == 'is null') { + this.whereNull(condition.column); + } else if (condition.operator == 'is not null') { + this.whereNotNull(condition.column); + } else { + this.where(condition.column, condition.operator, condition.value); + } + } + }); + + if (!results.length) return false; + } + + return true; + } + +} + +module.exports = DatabaseProvider; \ No newline at end of file diff --git a/lib/auth/provider.js b/lib/auth/provider.js new file mode 100644 index 0000000..7a2bde8 --- /dev/null +++ b/lib/auth/provider.js @@ -0,0 +1,149 @@ +const config = require('../setup/config'); +const crypto = require('crypto'); +const debug = require('debug')('server-connect:auth'); + +class AuthProvider { + + constructor(app, opts, name) { + this.app = app; + this.name = name; + this.identity = this.app.getSession(this.name + 'Id') || false; + this.secret = opts.secret || config.secret; + this.basicAuth = opts.basicAuth; + this.basicRealm = opts.basicRealm; + this.passwordVerify = opts.passwordVerify || false; + + this.cookieOpts = { + domain: opts.domain || undefined, + httpOnly: true, + maxAge: (opts.expires || 30) * 24 * 60 * 60 * 1000, // from days to ms + path: opts.path || '/', + secure: !!opts.secure, + sameSite: opts.sameSite || 'Strict', + signed: true + }; + } + + async autoLogin() { + if (this.basicAuth) { + const auth = require('../core/basicauth')(this.app.req); + debug(`Basic auth credentials received: %o`, auth); + if (auth) await this.login(auth.username, auth.password, false, true); + } + + const cookie = this.app.getCookie(this.name + '.auth', true); + if (cookie) { + const auth = this.decrypt(cookie); + debug(`Login with cookie: %o`, auth); + if (auth) await this.login(auth.username, auth.password, true, true); + } else { + debug(`No login cookie found`); + } + } + + async login(username, password, remember, autoLogin) { + const identity = await this.validate(username, password, this.passwordVerify); + + if (!identity) { + await this.logout(); + + if (!autoLogin) { + this.unauthorized(); + return false; + } + } else { + this.app.setSession(this.name + 'Id', identity); + this.app.set('identity', identity); + + if (remember) { + debug('setCookie', identity, username, password); + this.app.setCookie(this.name + '.auth', this.encrypt({ username, password }), this.cookieOpts); + } + + this.identity = identity; + } + + return identity; + } + + async logout() { + this.app.removeSession(this.name + 'Id'); + this.app.removeCookie(this.name + '.auth', this.cookieOpts); + this.app.remove('identity'); + this.identity = false; + } + + async restrict(opts) { + if (this.identity === false) { + if (opts.loginUrl) { + if (this.app.req.fragment) { + this.app.res.status(222).send(opts.loginUrl); + } else { + this.app.res.redirect(opts.loginUrl); + } + } else { + this.unauthorized(); + } + + return; + } + + if (opts.permissions) { + const allowed = await this.permissions(this.identity, opts.permissions); + if (!allowed) { + if (opts.forbiddenUrl) { + if (this.app.req.fragment) { + this.app.res.status(222).send(opts.forbiddenUrl); + } else { + this.app.res.redirect(opts.forbiddenUrl); + } + } else { + this.forbidden(); + } + } + } + } + + encrypt(data) { + const iv = crypto.randomBytes(16); + const key = crypto.scryptSync(this.secret, iv, 32); + const cipher = crypto.createCipheriv('aes-256-cbc', key, iv); + const encrypted = cipher.update(JSON.stringify(data), 'utf8', 'base64'); + return iv.toString('base64') + '.' + encrypted + cipher.final('base64'); + } + + decrypt(data) { + // try/catch to prevent errors on currupt cookies + try { + const iv = Buffer.from(data.split('.')[0], 'base64'); + const key = crypto.scryptSync(this.secret, iv, 32); + const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv); + const decrypted = decipher.update(data.split('.')[1], 'base64', 'utf8'); + return JSON.parse(decrypted + decipher.final('utf8')); + } catch (err) { + return undefined; + } + } + + unauthorized() { + if (this.basicAuth) { + this.app.res.set('WWW-Authenticate', `Basic Realm="${this.basicRealm}"`); + } + this.app.res.sendStatus(401); + } + + forbidden() { + this.app.res.sendStatus(403); + } + + async validate(username, password) { + throw new Error('auth.validate needs to be extended.'); + } + + async permissions(identity, permissions) { + throw new Error('auth.permissions needs to be extended.'); + } + +} + +module.exports = AuthProvider; \ No newline at end of file diff --git a/lib/auth/single.js b/lib/auth/single.js new file mode 100644 index 0000000..5b38e64 --- /dev/null +++ b/lib/auth/single.js @@ -0,0 +1,26 @@ +const AuthProvider = require('./provider'); +const debug = require('debug')('server-connect:auth'); + +class SingleProvider extends AuthProvider { + + constructor(app, opts, name) { + super(app, opts, name); + this.username = opts.username; + this.password = opts.password; + } + + validate(username, password) { + if (username == this.username && password == this.password) { + return username; + } + + return false; + } + + permissions() { + return true; + } + +} + +module.exports = SingleProvider; \ No newline at end of file diff --git a/lib/auth/static.js b/lib/auth/static.js new file mode 100644 index 0000000..ce1113f --- /dev/null +++ b/lib/auth/static.js @@ -0,0 +1,31 @@ +const AuthProvider = require('./provider'); + +class StaticProvider extends AuthProvider { + + constructor(app, opts, name) { + super(app, opts, name); + this.users = opts.users; + this.perms = opts.perms; + } + + validate(username, password) { + if (this.users[username] == password) { + return username; + } + + return false; + } + + permissions(username, permissions) { + for (let permission of permissions) { + if (!this.perms[permission] || !this.perms[permission].includes(username)) { + return false; + } + } + + return true; + } + +} + +module.exports = StaticProvider; \ No newline at end of file diff --git a/lib/core/app.js b/lib/core/app.js new file mode 100644 index 0000000..206a5b5 --- /dev/null +++ b/lib/core/app.js @@ -0,0 +1,713 @@ +const fs = require('fs-extra'); +const Scope = require('./scope'); +const Parser = require('./parser'); +const db = require('./db'); +const validator = require('../validator'); +const config = require('../setup/config'); +const debug = require('debug')('server-connect:app'); +const { clone, formatDate, parseDate } = require('../core/util'); +const { toSystemPath } = require('../core/path'); +const os = require('os'); + +if (!global.db) { + global.db = {}; +} + +function App(req = {}, res = {}) { + this.error = false; + this.data = {}; + this.meta = {}; + this.settings = {}; + this.modules = {}; + + this.io = global.io; + this.req = req; + this.res = res; + this.global = new Scope(); + this.scope = this.global; + + this.mail = {}; + this.auth = {}; + this.oauth = {}; + this.db = {}; + this.s3 = {}; + this.jwt = {}; + + if (config.globals.data) { + this.set(config.globals.data); + } + + this.set({ + $_ERROR: null, + //$_SERVER: process.env, + $_ENV: process.env, + $_GET: req.query, + $_POST: req.method == 'POST' ? req.body : {}, + $_PARAM: req.params, + $_HEADER: req.headers, + $_COOKIE: req.cookies, + $_SESSION: req.session + }); + + let urlParts = req.originalUrl ? req.originalUrl.split('?') : ['', '']; + let $server = { + CONTENT_TYPE: req.headers && req.headers['content-type'], + HTTPS: req.protocol == 'https', + PATH_INFO: req.path, + QUERY_STRING: urlParts[1], + REMOTE_ADDR: req.ip, + REQUEST_PROTOCOL: req.protocol, + REQUEST_METHOD: req.method, + SERVER_NAME: req.hostname, + BASE_URL: req.headers && req.protocol + '://' + req.headers['host'], + URL: urlParts[0], + HOSTNAME: os.hostname() + }; + + if (req.headers) { + for (let header in req.headers) { + $server['HTTP_' + header.toUpperCase().replace(/-/, '_')] = req.headers[header]; + } + } + + // Try to keep same as ASP/PHP + this.set('$_SERVER', $server); +} + +App.prototype = { + set: function(key, value) { + this.global.set(key, value); + }, + + get: function(key, def) { + let value = this.global.get(key); + return value !== undefined ? value : def; + }, + + remove: function(key) { + this.global.remove(key); + }, + + setSession: function(key, value) { + this.req.session[key] = value; + }, + + getSession: function(key) { + return this.req.session[key]; + }, + + removeSession: function(key) { + delete this.req.session[key]; + }, + + setCookie: function(name, value, opts) { + if (!this.res.headersSent) { + if (this.res.cookie) { + this.res.cookie(name, value, opts); + } + } else { + debug(`Trying to set ${name} cookie while headers were already sent.`); + } + }, + + getCookie: function(name, signed) { + return signed ? this.req.signedCookies[name] : this.req.cookies[name]; + }, + + removeCookie: function(name, opts) { + if (this.res.clearCookie) { + // copy all options except expires and maxAge + const clearOpts = Object.assign({}, opts); + delete clearOpts.expires; + delete clearOpts.maxAge; + this.res.clearCookie(name, clearOpts); + } + }, + + setMailer: function(name, options) { + let setup = {}; + + options = this.parse(options); + + switch (options.server) { + case 'mail': + setup.sendmail = true; + break; + + case 'ses': + const aws = require('@aws-sdk/client-ses'); + const ses = new AWS.SES({ + endpoint: options.endpoint, + credentials: { + accessKeyId: options.accessKeyId, + secretAccessKey: options.secretAccessKey + } + }); + setup.SES = { ses, aws }; + break; + + default: + // https://nodemailer.com/smtp/ + setup.host = options.host || 'localhost'; + setup.port = options.port || 25; + setup.secure = options.useSSL || false; + setup.auth = { + user: options.username, + pass: options.password + }; + setup.tls = { + rejectUnauthorized: false + }; + } + + return this.mail[name] = setup; + }, + + getMailer: function(name) { + if (this.mail[name]) { + return this.mail[name]; + } + + if (config.mail[name]) { + return this.setMailer(name, config.mail[name]); + } + + if (fs.existsSync(`app/modules/Mailer/${name}.json`)) { + let action = fs.readJSONSync(`app/modules/Mailer/${name}.json`); + return this.setMailer(name, action.options); + } + + throw new Error(`Couldn't find mailer "${name}".`); + }, + + setAuthProvider: async function(name, options) { + const Provider = require('../auth/' + options.provider.toLowerCase()); + const provider = new Provider(this, options, name); + if (!provider.identity) await provider.autoLogin(); + this.set('identity', provider.identity); + return this.auth[name] = provider; + }, + + getAuthProvider: async function(name) { + if (this.auth[name]) { + return this.auth[name]; + } + + if (config.auth[name]) { + return await this.setAuthProvider(name, config.auth[name]); + } + + if (fs.existsSync(`app/modules/SecurityProviders/${name}.json`)) { + let action = fs.readJSONSync(`app/modules/SecurityProviders/${name}.json`); + return await this.setAuthProvider(name, action.options); + } + + throw new Error(`Couldn't find security provider "${name}".`); + }, + + setOAuthProvider: async function(name, options) { + const OAuth2 = require('../oauth'); + const services = require('../oauth/services'); + + options = this.parse(options); + + let service = this.parseOptional(options.service, 'string', null); + let opts = service ? services[service] : {}; + opts.client_id = this.parseOptional(options.client_id, 'string', null); + opts.client_secret = this.parseOptional(options.client_secret, 'string', null); + opts.token_endpoint = opts.token_endpoint || this.parseRequired(options.token_endpoint, 'string', 'oauth.provider: token_endpoint is required.'); + opts.auth_endpoint = opts.auth_endpoint || this.parseOptional(options.auth_endpoint, 'string', ''); + opts.scope_separator = opts.scope_separator || this.parseOptional(options.scope_separator, 'string', ' '); + opts.access_token = this.parseOptional(options.access_token, 'string', null); + opts.refresh_token = this.parseOptional(options.refresh_token, 'string', null); + opts.jwt_bearer = this.parseOptional(options.jwt_bearer, 'string', false); + opts.client_credentials = this.parseOptional(options.client_credentials, 'boolean', false); + opts.params = Object.assign({}, opts.params, this.parseOptional(options.params, 'object', {})); + + this.oauth[name] = new OAuth2(this, this.parse(options), name); + await this.oauth[name].init(); + + return this.oauth[name]; + }, + + getOAuthProvider: async function(name) { + if (this.oauth[name]) { + return this.oauth[name]; + } + + if (config.oauth[name]) { + return await this.setOAuthProvider(name, config.oauth[name]); + } + + if (fs.existsSync(`app/modules/oauth/${name}.json`)) { + let action = fs.readJSONSync(`app/modules/oauth/${name}.json`); + return await this.setOAuthProvider(name, action.options); + } + + throw new Error(`Couldn't find oauth provider "${name}".`); + }, + + setDbConnection: function(name, options) { + if (global.db[name]) { + return global.db[name]; + } + + options = this.parse(options); + + switch (options.client) { + case 'couchdb': + const { host, port, user, password, database } = options.connection; + const nano = require('nano'); + global.db[name] = nano(`http://${user ? user + (password ? ':' + password : '') + '@' : ''}${host}${port ? ':' + port : ''}/${database}`); + global.db[name].client = options.client; + return global.db[name]; + + case 'sqlite3': + if (options.connection.filename) { + options.connection.filename = toSystemPath(options.connection.filename); + } + break; + + case 'mysql': + case 'mysql2': + options.connection = { + supportBigNumber: true, + dateStrings: !!options.tz, + ...options.connection + }; + break; + + case 'mssql': + if (options.tz) { + // use local timezone to have same behavior as the other drivers + // to prevent problems node should have same timezone as database + options.connection.options = { useUTC: false, ...options.connection.options }; + } + break; + + case 'postgres': + if (options.tz) { + const types = require('pg').types; + const parseFn = val => val; + types.setTypeParser(types.builtins.TIME, parseFn); + types.setTypeParser(types.builtins.TIMETZ, parseFn); + types.setTypeParser(types.builtins.TIMESTAMP, parseFn); + types.setTypeParser(types.builtins.TIMESTAMPTZ, parseFn); + } + break; + } + + if (options.connection && options.connection.ssl) { + if (options.connection.ssl.key) { + options.connection.ssl.key = fs.readFileSync(toSystemPath(options.connection.ssl.key)); + } + + if (options.connection.ssl.ca) { + options.connection.ssl.ca = fs.readFileSync(toSystemPath(options.connection.ssl.ca)); + } + + if (options.connection.ssl.cert) { + options.connection.ssl.cert = fs.readFileSync(toSystemPath(options.connection.ssl.cert)); + } + } + + options.useNullAsDefault = true; + + const formatRecord = (record, meta) => { + for (column in record) { + if (record[column] != null) { + if (meta.has(column)) { + const info = meta.get(column); + + if (['json', 'object', 'array'].includes(info.type)) { + if (typeof record[column] == 'string') { + try { + // column of type json returned as string, need parse + record[column] = JSON.parse(record[column]); + } catch (err) { + console.warn(err); + } + } + } + + if (info.type == 'date') { + if (typeof record[column] == 'string' && record[column].startsWith('0000-00-00')) { + record[column] = undefined; + } else { + record[column] = formatDate(record[column], 'yyyy-MM-dd'); + } + } + + if (info.type == 'time') { + if (options.tz == 'local') { + record[column] = formatDate(record[column], 'HH:mm:ss.v'); + } else if (options.tz == 'utc') { + record[column] = parseDate(parseDate(formatDate('now', 'yyyy-MM-dd ') + formatDate(record[column], 'HH:mm:ss.v'))).toISOString().slice(11); + } + } + + if (['datetime', 'timestamp'].includes(info.type) || Object.prototype.toString.call(record[column]) == '[object Date]') { + if (typeof record[column] == 'string' && record[column].startsWith('0000-00-00')) { + record[column] = undefined; + } else if (options.tz == 'local') { + record[column] = formatDate(record[column], 'yyyy-MM-dd HH:mm:ss.v'); + } else if (options.tz == 'utc') { + record[column] = parseDate(record[column]).toISOString(); + } + } + } else { + // try to detect datetime + if (Object.prototype.toString.call(record[column]) == '[object Date]') { + if (options.tz == 'local') { + record[column] = formatDate(record[column], 'yyyy-MM-dd HH:mm:ss.v'); + } else if (options.tz == 'utc') { + record[column] = parseDate(record[column]).toISOString(); + } + } else if (typeof record[column] == 'string' && /\d{4}-\d{2}-\d{2}([T\s]\d{2}:\d{2}:\d{2}(\.\d+)?([Zz]|[+-]\d{2}(:\d{2})?)?)?/.test(record[column])) { + if (record[column].startsWith('0000-00-00')) { + record[column] = undefined; + } else if (options.tz == 'local') { + record[column] = formatDate(record[column], 'yyyy-MM-dd HH:mm:ss.v'); + } else if (options.tz == 'utc') { + record[column] = parseDate(record[column]).toISOString(); + } + } + } + + if (record[column] == undefined) { + record[column] = null; + } else if (record[column].toJSON) { + record[column] = record[column].toJSON(); + } + } + } + + return record; + }; + + options.postProcessResponse = function(result, queryContext) { + const meta = new Map(); + + if (Array.isArray(queryContext)) { + for (let item of queryContext) { + if (item.name && item.type) { + meta.set(item.name, item); + } + } + } + + if (Array.isArray(result)) { + return result.map(record => formatRecord(record, meta)); + } else { + return formatRecord(result, meta); + } + }; + + global.db[name] = db(options); + //this.res.on('finish', () => global.db[name].destroy()); + + return global.db[name]; + }, + + getDbConnection: function(name) { + if (this.__trx) return this.__trx; + + if (global.db[name]) { + return global.db[name]; + } + + if (config.db[name]) { + return this.setDbConnection(name, config.db[name]); + } + + if (fs.existsSync(`app/modules/connections/${name}.json`)) { + let action = fs.readJSONSync(`app/modules/connections/${name}.json`); + return this.setDbConnection(name, action.options); + } + + throw new Error(`Couldn't find database connection "${name}".`); + }, + + setS3Provider: function(name, options) { + options = this.parse(options); + + const { S3 } = require('@aws-sdk/client-s3'); + const endpoint = this.parseRequired(options.endpoint, 'string', 's3.provider: endpoint is required.'); + const accessKeyId = this.parseRequired(options.accessKeyId, 'string', 's3.provider: accessKeyId is required.'); + const secretAccessKey = this.parseRequired(options.secretAccessKey, 'string', 's3.provider: secretAccessKey is required.'); + + let region = options.region || 'us-east-1'; + let pos = endpoint.indexOf('.amazonaws'); + if (pos > 3) region = endpoint.substr(3, pos - 3); + let forcePathStyle = options.forcePathStyle || false; + + this.s3[name] = new S3({ endpoint: 'https://' + endpoint, credentials: { accessKeyId, secretAccessKey }, region, signatureVersion: 'v4', forcePathStyle }); + + return this.s3[name]; + }, + + getS3Provider: function(name) { + if (this.s3[name]) { + return this.s3[name]; + } + + if (config.s3[name]) { + return this.setS3Provider(name, config.s3[name]); + } + + if (fs.existsSync(`app/modules/s3/${name}.json`)) { + let action = fs.readJSONSync(`app/modules/s3/${name}.json`); + return this.setS3Provider(name, action.options); + } + + throw new Error(`Couldn't find S3 provider "${name}".`); + }, + + setJSONWebToken: function(name, options) { + const jwt = require('jsonwebtoken'); + + options = this.parse(options); + + let opts = {}; + + if (options.alg) opts.algorithm = options.alg; + if (options.iss) opts.issuer = options.iss; + if (options.sub) opts.subject = options.sub; + if (options.aud) opts.audience = options.aud; + if (options.jti) opts.jwtid = options.jti; + if (options.expiresIn) opts.expiresIn = options.expiresIn + + return this.jwt[name] = jwt.sign({ + ...options.claims + }, options.key, { + expiresIn: 3600, // use as default (required by google) + ...opts + }); + }, + + getJSONWebToken: function(name) { + if (config.jwt[name]) { + return this.setJSONWebToken(name, config.jwt[name]); + } + + if (fs.existsSync(`app/modules/jwt/${name}.json`)) { + let action = fs.readJSONSync(`app/modules/jwt/${name}.json`); + return this.setJSONWebToken(name, action.options); + } + + throw new Error(`Couldn't find JSON Web Token "${name}".`); + }, + + define: async function(cfg, internal) { + if (cfg.settings) { + this.settings = clone(cfg.settings); + } + + if (cfg.vars) { + this.set(clone(cfg.vars)); + } + + if (cfg.meta) { + this.meta = clone(cfg.meta); + await validator.init(this, this.meta); + } + + if (fs.existsSync('app/modules/global.json')) { + await this.exec(await fs.readJSON('app/modules/global.json'), true); + } + + /* + debug('body: %o', this.req.body); + debug('query: %o', this.req.query); + debug('params: %o', this.req.params); + debug('headers: %o', this.req.headers); + debug('cookies: %o', this.req.cookies); + debug('session: %o', this.req.session); + */ + + await this.exec(cfg.exec || cfg, internal); + }, + + sub: async function(actions, scope) { + const subApp = new App(this.req, this.res); + subApp.global = this.global; + subApp.scope = this.scope.create(scope); + await subApp.exec(actions, true); + return subApp.data; + }, + + exec: async function(actions, internal) { + if (actions.exec) { + return this.exec(actions.exec, internal); + } + + actions = clone(actions); + + await this._exec(actions.steps || actions); + + if (this.error !== false) { + if (actions.catch) { + this.scope.set('$_ERROR', this.error.message); + this.error = false; + await this._exec(actions.catch, true); + } else { + throw this.error; + } + } + + if (!internal && !this.res.headersSent && !this.noOutput) { + this.res.json(this.data); + } + }, + + _exec: async function(steps, ignoreAbort) { + if (this.res.headersSent) return; + + if (typeof steps == 'string') { + return this.exec(await fs.readJSON(`app/modules/${steps}.json`), true); + } + + if (this.res.headersSent) { + // do not execute other steps after headers has been sent + return; + } + + if (Array.isArray(steps)) { + for (let step of steps) { + await this._exec(step); + if (this.error) return; + } + return; + } + + if (steps.disabled) { + return; + } + + if (steps.action) { + try { + let module; + + if (fs.existsSync(`extensions/server_connect/modules/${steps.module}.js`)) { + module = require(`../../extensions/server_connect/modules/${steps.module}`); + } else if (fs.existsSync(`lib/modules/${steps.module}.js`)) { + module = require(`../modules/${steps.module}`); + } else { + throw new Error(`Module ${steps.module} doesn't exist`); + } + + if (typeof module[steps.action] != 'function') { + throw new Error(`Action ${steps.action} doesn't exist in ${steps.module || 'core'}`); + } + + debug(`Executing action step ${steps.action}`); + debug(`options: %O`, steps.options); + + if (!ignoreAbort && config.abortOnDisconnect && this.req.isDisconnected) { + throw new Error('Aborted'); + } + + const data = await module[steps.action].call(this, clone(steps.options), steps.name, steps.meta); + + if (data instanceof Error) { + throw data; + } + + if (steps.name) { + this.scope.set(steps.name, data); + + if (steps.output) { + this.data[steps.name] = data; + } + } + } catch (e) { + this.error = e; + return; + } + } + }, + + parse: function(value, scope) { + return Parser.parseValue(value, scope || this.scope); + }, + + parseRequired: function(value, type, err) { + if (value === undefined) { + throw new Error(err); + } + + let val = Parser.parseValue(value, this.scope); + + if (type == '*') { + if (val === undefined) { + throw new Error(err); + } + } else if (type == 'boolean') { + val = !!val; + } else if (typeof val != type) { + throw new Error(err); + } + + return val; + }, + + parseOptional: function(value, type, def) { + if (value === undefined) return def; + + let val = Parser.parseValue(value, this.scope); + + if (type == '*') { + if (val === undefined) val = def; + } else if (type == 'boolean') { + if (val === undefined) { + val = def; + } else { + val = !!val; + } + } else if (typeof val != type) { + val = def; + } + + return val; + }, + + parseSQL: function(sql) { + if (!sql) return null; + + ['values', 'orders'].forEach((prop) => { + if (Array.isArray(sql[prop])) { + sql[prop] = sql[prop].filter((value) => { + if (!value.condition) return true; + return !!Parser.parseValue(value.condition, this.scope); + }); + } + }); + + if (sql.wheres && sql.wheres.rules) { + if (sql.wheres.conditional && !Parser.parseValue(sql.wheres.conditional, this.scope)) { + delete sql.wheres; + } else { + sql.wheres.rules = sql.wheres.rules.filter(function filterConditional(rule) { + if (!rule.rules) return true; + if (rule.conditional && !Parser.parseValue(rule.conditional, this.scope)) return false; + rule.rules = rule.rules.filter(filterConditional, this); + return rule.rules.length; + }, this); + + if (!sql.wheres.rules.length) { + delete sql.wheres; + } + } + } + + if (sql.sub) { + for (const name in sql.sub) { + sql.sub[name] = this.parseSQL(sql.sub[name]); + } + } + + return Parser.parseValue(sql, this.scope); + }, +}; + +module.exports = App; \ No newline at end of file diff --git a/lib/core/async.js b/lib/core/async.js new file mode 100644 index 0000000..d25e20d --- /dev/null +++ b/lib/core/async.js @@ -0,0 +1,97 @@ +function defered() { + let resolve, reject; + let promise = new Promise((_resolve, _reject) => { + resolve = _resolve; + reject = _reject; + }); + + return { + promise, + resolve, + reject + }; +} + +function limit(concurrency) { + let running = 0; + let queue = []; + + function init() { + if (running < concurrency) { + running++; + return Promise.resolve(); + } + + let defer = defered(); + queue.push(defer); + return defer.promise; + } + + function complete(a) { + let next = queue.shift(); + + if (next) { + next.resolve(); + } else { + running--; + } + + return a; + } + + return function(fn) { + return function() { + let args = Array.prototype.slice.apply(arguments); + + return init().then(() => { + return fn.apply(null, args); + }).finally(complete); + } + } +} + +module.exports = { + + map: function(arr, fn, concurrency) { + arr = Array.isArray(arr) ? arr : [arr]; + + if (concurrency) { + const limiter = limit(concurrency) + return Promise.all(arr.map(limiter(fn))); + } + + return Promise.all(arr.map(fn)); + }, + + mapSeries: function(arr, fn) { + arr = Array.isArray(arr) ? arr : [arr]; + + return arr.reduce((promise, curr, index, arr) => { + return promise.then(prev => { + return fn(curr, index, arr).then(val => { + prev.push(val); + return prev; + }); + }) + }, Promise.resolve([])); + }, + + reduce: function(arr, fn, start) { + arr = Array.isArray(arr) ? arr : [arr]; + + if (!arr.length) { + return Promise.resolve(start); + } + + return arr.reduce((promise, curr, index, arr) => { + return promise.then(prev => { + if (prev === undefined && arr.length === 1) { + return curr; + } + + return fn(prev, curr, index, arr); + }); + }, Promise.resolve(start)); + }, + +}; \ No newline at end of file diff --git a/lib/core/basicauth.js b/lib/core/basicauth.js new file mode 100644 index 0000000..e712232 --- /dev/null +++ b/lib/core/basicauth.js @@ -0,0 +1,43 @@ +// Code taken from https://github.com/jshttp/basic-auth/blob/master/index.js +// Copyright(c) 2013 TJ Holowaychuk +// Copyright(c) 2014 Jonathan Ong +// Copyright(c) 2015-2016 Douglas Christopher Wilson + +module.exports = auth; +module.exports.parse = parse; + +const CREDENTIALS_REGEXP = /^ *(?:[Bb][Aa][Ss][Ii][Cc]) +([A-Za-z0-9._~+/-]+=*) *$/; +const USER_PASS_REGEXP = /^([^:]*):(.*)$/; + +function auth(req) { + if (!req) throw new Error('argument req is required.'); + if (typeof req != 'object') throw new Error('argument req needs to be an object.'); + + return parse(getAuthorization(req)); +} + +function decodeBase64(str) { + return Buffer.from(str, 'base64').toString(); +} + +function getAuthorization(req) { + if (!req.headers || typeof req.headers != 'object') { + throw new Error('argument req needs to have headers.'); + } + + return req.headers.authorization; +} + +function parse(str) { + if (typeof str != 'string') { + return undefined; + } + + const match = CREDENTIALS_REGEXP.exec(str); + if (!match) return undefined; + + const userPass = USER_PASS_REGEXP.exec(decodeBase64(match[1])); + if (!userPass) return undefined; + + return { username: userPass[1], password: userPass[2] }; +} \ No newline at end of file diff --git a/lib/core/db.js b/lib/core/db.js new file mode 100644 index 0000000..3592dd3 --- /dev/null +++ b/lib/core/db.js @@ -0,0 +1,210 @@ +const knex = require('knex'); + +knex.QueryBuilder.extend('whereGroup', function(condition, rules) { + condition = condition.toLowerCase(); + + // TODO: Escape likes + + for (let rule of rules) { + if (!rule.condition) { + let where = condition == 'or' ? 'orWhere' : 'where'; + let column = rule.data ? rule.data.column : rule.column; + + if (rule.data && rule.data.table) { + if (rule.data.schema) { + column = rule.data.schema + '.' + rule.data.table + '.' + column; + } else { + column = rule.data.table + '.' + column; + } + } else if (rule.table) { + if (rule.schema) { + column = rule.schema + '.' + rule.table + '.' + column; + } else { + column = rule.table + '.' + column; + } + } + + if (typeof rule.value == 'undefined') { + rule.value = null; + } + + if (rule.operator == 'between') { + this[where + 'Between'](column, rule.value); + } else if (rule.operator == 'not_between') { + this[where + 'NotBetween'](column, rule.value); + } else if (rule.operator == 'is_null') { + this[where + 'Null'](column); + } else if (rule.operator == 'is_not_null') { + this[where + 'NotNull'](column); + } else if (rule.operator == 'in') { + this[where + 'In'](column, rule.value); + } else if (rule.operator == 'not_in') { + this[where + 'NotIn'](column, rule.value); + } else if (rule.operator == 'begins_with') { + this[where](column, 'like', rule.value + '%'); + } else if (rule.operator == 'not_begins_with') { + this[where + 'Not'](column, 'like', rule.value + '%'); + } else if (rule.operator == 'ends_with') { + this[where](column, 'like', '%' + rule.value); + } else if (rule.operator == 'not_ends_with') { + this[where + 'Not'](column, 'like', '%' + rule.value); + } else if (rule.operator == 'contains') { + this[where](column, 'like', '%' + rule.value + '%'); + } else if (rule.operator == 'not_contains') { + this[where + 'Not'](column, 'like', '%' + rule.value + '%'); + } else { + this[where](column, rule.operation, rule.value); + } + } else { + this[condition + 'Where'](function() { + this.whereGroup(rule.condition, rule.rules); + }); + } + } + + return this; +}); + +knex.QueryBuilder.extend('fromJSON', function(ast, meta) { + if (ast.type == 'count') { + return this.count('* as Total').from(function() { + this.fromJSON(Object.assign({}, ast, { + type: 'select', + offset: null, + limit: null, + orders: null + })).as('t1'); + }).first(); + } else if (ast.type == 'insert' || ast.type == 'update') { + let values = {}; + + for (let val of ast.values) { + if (val.type == 'json') { + // json support + values[val.column] = JSON.stringify(val.value); + } else { + values[val.column] = val.value; + } + } + + this[ast.type](values); + } else { + this[ast.type](); + } + + if (ast.returning) { + // Adding the option includeTriggerModifications allows you + // to run statements on tables that contain triggers. Only affects MSSQL. + this.returning(ast.returning, { includeTriggerModifications: true }); + } + + if (ast.table) { + let table = ast.table.name || ast.table; + + if (ast.table.schema) { + table = ast.table.schema + '.' + table; + } + + if (ast.table.alias) { + table += ' as ' + ast.table.alias; + } + + this.from(table); + } + + if ((ast.type == 'select' || ast.type == 'first') && ast.joins && ast.joins.length) { + for (let join of ast.joins) { + let table = join.table; + + if (join.schema) { + table = join.schema + '.' + table; + } + + if (join.alias) { + table += ' as ' + join.alias; + } + + this[(join.type || 'inner').toLowerCase() + 'Join'](table, function() { + for (let clause of join.clauses.rules) { + this.on( + (clause.schema ? clause.schema + '.' : '') + clause.table + '.' + clause.column, + clause.operation, + (clause.value.schema ? clause.value.schema + '.' : '') + clause.value.table + '.' + clause.value.column + ); + } + }); + } + } + + if ((ast.type == 'select' || ast.type == 'first') && ast.columns) { + for (let col of ast.columns) { + let column = col.column || col; + + if (ast.joins && ast.joins.length && col.table) { + column = col.table + '.' + column; + + if (col.schema) { + column = col.schema + '.' + column; + } + } + + if (col.alias) { + column += ' as ' + col.alias; + } + + this[col.aggregate ? col.aggregate.toLowerCase() : 'column'](column); + } + } + + if ((ast.type == 'select' || ast.type == 'first') && ast.groupBy && ast.groupBy.length) { + for (let col of ast.groupBy) { + if (ast.joins && ast.joins.length && col.table) { + if (col.schema) { + this.groupBy(col.schema + '.' + col.table + '.' + col.column); + } else { + this.groupBy(col.table + '.' + col.column); + } + } else { + this.groupBy(col.column || col); + } + } + } + + if (ast.wheres && ast.wheres.condition) { + this.whereGroup(ast.wheres.condition, ast.wheres.rules); + } + + if ((ast.type == 'select' || ast.type == 'first') && ast.orders && ast.orders.length) { + for (let order of ast.orders) { + if (ast.joins && ast.joins.length && order.table) { + if (order.schema) { + this.orderBy(order.schema + '.' + order.table + '.' + order.column, order.direction); + } else { + this.orderBy(order.table + '.' + order.column, order.direction); + } + } else { + this.orderBy(order.column, order.direction); + } + } + } + + if (ast.distinct) { + this.distinct(); + } + + if (ast.type == 'select' && ast.limit) { + this.limit(ast.limit); + } + + if (ast.type == 'select' && ast.offset) { + this.offset(ast.offset); + } + + if (meta) { + this.queryContext(meta); + } + + return this; +}); + +module.exports = knex; \ No newline at end of file diff --git a/lib/core/diacritics.js b/lib/core/diacritics.js new file mode 100644 index 0000000..8f4caa4 --- /dev/null +++ b/lib/core/diacritics.js @@ -0,0 +1,96 @@ +const diacriticsMap = [ + {'base':'A', 'letters':/[\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u00C4\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F]/g}, + {'base':'AA','letters':/[\uA732]/g}, + {'base':'AE','letters':/[\u00C6\u01FC\u01E2]/g}, + {'base':'AO','letters':/[\uA734]/g}, + {'base':'AU','letters':/[\uA736]/g}, + {'base':'AV','letters':/[\uA738\uA73A]/g}, + {'base':'AY','letters':/[\uA73C]/g}, + {'base':'B', 'letters':/[\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181]/g}, + {'base':'C', 'letters':/[\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E]/g}, + {'base':'D', 'letters':/[\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779]/g}, + {'base':'DZ','letters':/[\u01F1\u01C4]/g}, + {'base':'Dz','letters':/[\u01F2\u01C5]/g}, + {'base':'E', 'letters':/[\u0045\u24BA\uFF25\u00C8\u00C9\u00CA\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u0112\u1E14\u1E16\u0114\u0116\u00CB\u1EBA\u011A\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u0118\u1E18\u1E1A\u0190\u018E]/g}, + {'base':'F', 'letters':/[\u0046\u24BB\uFF26\u1E1E\u0191\uA77B]/g}, + {'base':'G', 'letters':/[\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E]/g}, + {'base':'H', 'letters':/[\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D]/g}, + {'base':'I', 'letters':/[\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197]/g}, + {'base':'J', 'letters':/[\u004A\u24BF\uFF2A\u0134\u0248]/g}, + {'base':'K', 'letters':/[\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2]/g}, + {'base':'L', 'letters':/[\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780]/g}, + {'base':'LJ','letters':/[\u01C7]/g}, + {'base':'Lj','letters':/[\u01C8]/g}, + {'base':'M', 'letters':/[\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C]/g}, + {'base':'N', 'letters':/[\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4]/g}, + {'base':'NJ','letters':/[\u01CA]/g}, + {'base':'Nj','letters':/[\u01CB]/g}, + {'base':'O', 'letters':/[\u004F\u24C4\uFF2F\u00D2\u00D3\u00D4\u1ED2\u1ED0\u1ED6\u1ED4\u00D5\u1E4C\u022C\u1E4E\u014C\u1E50\u1E52\u014E\u022E\u0230\u00D6\u022A\u1ECE\u0150\u01D1\u020C\u020E\u01A0\u1EDC\u1EDA\u1EE0\u1EDE\u1EE2\u1ECC\u1ED8\u01EA\u01EC\u00D8\u01FE\u0186\u019F\uA74A\uA74C]/g}, + {'base':'OI','letters':/[\u01A2]/g}, + {'base':'OO','letters':/[\uA74E]/g}, + {'base':'OU','letters':/[\u0222]/g}, + {'base':'P', 'letters':/[\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754]/g}, + {'base':'Q', 'letters':/[\u0051\u24C6\uFF31\uA756\uA758\u024A]/g}, + {'base':'R', 'letters':/[\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782]/g}, + {'base':'S', 'letters':/[\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784]/g}, + {'base':'T', 'letters':/[\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786]/g}, + {'base':'TZ','letters':/[\uA728]/g}, + {'base':'U', 'letters':/[\u0055\u24CA\uFF35\u00D9\u00DA\u00DB\u0168\u1E78\u016A\u1E7A\u016C\u00DC\u01DB\u01D7\u01D5\u01D9\u1EE6\u016E\u0170\u01D3\u0214\u0216\u01AF\u1EEA\u1EE8\u1EEE\u1EEC\u1EF0\u1EE4\u1E72\u0172\u1E76\u1E74\u0244]/g}, + {'base':'V', 'letters':/[\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245]/g}, + {'base':'VY','letters':/[\uA760]/g}, + {'base':'W', 'letters':/[\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72]/g}, + {'base':'X', 'letters':/[\u0058\u24CD\uFF38\u1E8A\u1E8C]/g}, + {'base':'Y', 'letters':/[\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE]/g}, + {'base':'Z', 'letters':/[\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762]/g}, + {'base':'a', 'letters':/[\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u00E4\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250]/g}, + {'base':'aa','letters':/[\uA733]/g}, + {'base':'ae','letters':/[\u00E6\u01FD\u01E3]/g}, + {'base':'ao','letters':/[\uA735]/g}, + {'base':'au','letters':/[\uA737]/g}, + {'base':'av','letters':/[\uA739\uA73B]/g}, + {'base':'ay','letters':/[\uA73D]/g}, + {'base':'b', 'letters':/[\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253]/g}, + {'base':'c', 'letters':/[\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184]/g}, + {'base':'d', 'letters':/[\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A]/g}, + {'base':'dz','letters':/[\u01F3\u01C6]/g}, + {'base':'e', 'letters':/[\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD]/g}, + {'base':'f', 'letters':/[\u0066\u24D5\uFF46\u1E1F\u0192\uA77C]/g}, + {'base':'g', 'letters':/[\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F]/g}, + {'base':'h', 'letters':/[\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265]/g}, + {'base':'hv','letters':/[\u0195]/g}, + {'base':'i', 'letters':/[\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131]/g}, + {'base':'j', 'letters':/[\u006A\u24D9\uFF4A\u0135\u01F0\u0249]/g}, + {'base':'k', 'letters':/[\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3]/g}, + {'base':'l', 'letters':/[\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747]/g}, + {'base':'lj','letters':/[\u01C9]/g}, + {'base':'m', 'letters':/[\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F]/g}, + {'base':'n', 'letters':/[\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5]/g}, + {'base':'nj','letters':/[\u01CC]/g}, + {'base':'o', 'letters':/[\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u00F6\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275]/g}, + {'base':'oi','letters':/[\u01A3]/g}, + {'base':'ou','letters':/[\u0223]/g}, + {'base':'oo','letters':/[\uA74F]/g}, + {'base':'p','letters':/[\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755]/g}, + {'base':'q','letters':/[\u0071\u24E0\uFF51\u024B\uA757\uA759]/g}, + {'base':'r','letters':/[\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783]/g}, + {'base':'s','letters':/[\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B]/g}, + {'base':'t','letters':/[\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787]/g}, + {'base':'tz','letters':/[\uA729]/g}, + {'base':'u','letters':/[\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u00FC\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289]/g}, + {'base':'v','letters':/[\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C]/g}, + {'base':'vy','letters':/[\uA761]/g}, + {'base':'w','letters':/[\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73]/g}, + {'base':'x','letters':/[\u0078\u24E7\uFF58\u1E8B\u1E8D]/g}, + {'base':'y','letters':/[\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF]/g}, + {'base':'z','letters':/[\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763]/g} +]; + +module.exports = { + replace: function(str) { + for (let change of diacriticsMap) { + str = str.replace(change.letters, change.base); + } + + return str; + } +}; \ No newline at end of file diff --git a/lib/core/memoryStore.js b/lib/core/memoryStore.js new file mode 100644 index 0000000..767fc4e --- /dev/null +++ b/lib/core/memoryStore.js @@ -0,0 +1,92 @@ +module.exports = function(session) { + const Store = session.Store + + const defer = (cb, ...args) => { + if (typeof cb != 'function') return + setImmediate(cb, ...args) + } + + class ExtendedMap extends Map { + set(key, value, maxAge) { + const cached = super.get(key) + if (cached && cached.timer) clearTimeout(cached.timer) + const timer = maxAge ? setTimeout(super.delete.bind(this, key), maxAge) : null + return super.set(key, {timer, value}) + } + + get(key) { + const cached = super.get(key) + return cached && cached.value + } + + delete(key) { + const cached = super.get(key) + if (cached && cached.timer) clearTimeout(cached.timer) + return super.delete(key) + } + } + + class MemoryStore extends Store { + constructor(options = {}) { + super(options) + this.sessions = new ExtendedMap() + this.ttl = options.ttl + } + + get(sid, callback) { + let session = this.sessions.get(sid) + defer(callback, null, session) + } + + set(sid, session, callback) { + let ttl = this._getTTL(session) + if (ttl > 0) { + this.sessions.set(sid, session, ttl) + } else { + this.sessions.delete(sid) + } + defer(callback, null) + } + + touch(sid, session, callback) { + let ttl = this._getTTL(session) + let stored = this.sessions.get(sid) + stored.cookie = session.cookie + this.sessions.set(sid, stored, ttl) + defer(callback, null) + } + + destroy(sid, callback) { + this.sessions.delete(sid) + defer(callback, null) + } + + clear(callback) { + this.sessions.clear() + defer(callback, null) + } + + length(callback) { + let len = this.sessions.size + defer(callback, null, len) + } + + ids(callback) { + let keys = Array.from(this.sessions.keys()) + defer(callback, null, keys) + } + + all(callback) { + let sessions = Array.from(this.sessions.values()) + defer(callback, null, sessions) + } + + _getTTL(session) { + if (typeof this.ttl == 'number') return this.ttl + let maxAge = (session && session.cookie) ? session.cookie.maxAge : null + return (typeof maxAge == 'number') ? maxAge : 86400000 + } + } + + return MemoryStore +} \ No newline at end of file diff --git a/lib/core/middleware.js b/lib/core/middleware.js new file mode 100644 index 0000000..6221f05 --- /dev/null +++ b/lib/core/middleware.js @@ -0,0 +1,151 @@ +const config = require('../setup/config'); +const debug = require('debug')('server-connect:router'); +const { clone } = require('./util'); +const App = require('./app'); + +module.exports = { + cache: function(options) { + return async function(req, res, next) { + if (!options.ttl || !global.redisClient) { + // no caching + return next(); + } + + let key = 'erc:' + (req.originalUrl || req.url); + let nocache = false; + let nostore = false; + + if (req.fragment) { + key += '-fragment'; + } + + if (req.query.nocache) { + nocache = true; + } + + try { + const cacheControl = req.get('Cache-Control'); + if (cacheControl) { + if (cacheControl.includes('no-cache')) nocache = true; + if (cacheControl.includes('no-store')) nostore = true; + } + } catch (err) { + // Ignore but log errors + console.error(err); + } + + if (!nocache) { + try { + const cache = await global.redisClient.hGetAll(key); + if (cache.body) { + res.type(cache.type || 'text/html'); + res.send(cache.body); + return; + } + } catch (err) { + // Ignore but log errors + console.error(err); + } + } + + if (!nostore) { + // wrap res.send + const send = res.send.bind(res); + res.send = function (body) { + const ret = send(body); + + if (this.statusCode !== 200 || typeof body !== 'string') { + // do not cache when not status 200 or body is not a string + return ret; + } + + global.redisClient.hSet(key, { + body: body, + type: this.get('Content-Type') || 'text/html' + }).then(() => { + return global.redisClient.expire(key, +options.ttl); + }).catch(err => { + // Ignore but log errors + console.error(err); + }); + + return ret; + }; + } + + return next(); + }; + }, + + serverConnect: function(json) { + return async function(req, res, next) { + const app = new App(req, res); + + debug(`Serving serverConnect ${req.path}`); + + return Promise.resolve(app.define(json)).catch(next); + }; + }, + + templateView: function(layout, page, data, exec) { + return async function(req, res, next) { + const app = new App(req, res); + + debug(`Serving templateView ${req.path}`); + + const routeData = clone(data); + const methods = { + _: (expr, data = {}) => { + return app.parse(`{{${expr}}}`, app.scope.create(data)); + }, + + _exec: async (name, steps, data = {}) => { + let context = {}; + app.scope = app.scope.create(data, context); + await app.exec(steps, true); + app.scope = app.scope.parent; + app.set(name, context); + return context; + }, + + _repeat: (expr, cb) => { + let data = app.parse(`{{${expr}}}`); + if (Array.isArray(data)) return data.forEach(cb); + return ''; + }, + + _route: req.route.path + }; + + let template = page; + if (layout && !req.fragment) { + routeData.content = '/' + page; + template = 'layouts/' + layout; + } + + if (exec) { + return Promise.resolve(app.define(exec, true)).then(() => { + if (!res.headersSent) { + app.set(app.parse(routeData)); + debug(`Render template ${template}`); + debug(`Template data: %O`, Object.assign({}, app.global.data, app.data)); + res.render(template, Object.assign({}, app.global.data, app.data, methods), async (err, html) => { + // callback is needed when using ejs with aync, html is a promise + if (err) return next(err); + html.then(html => res.send(html)).catch(err => next(err)); + }); + } + }).catch(next) + } else { + app.set(app.parse(routeData)); + debug(`Render template ${template}`); + debug(`Template data: %O`, app.global.data); + res.render(template, Object.assign({}, app.global.data, methods), async (err, html) => { + // callback is needed when using ejs with aync, html is a promise + if (err) return next(err); + html.then(html => res.send(html)).catch(err => next(err)); + }); + } + }; + } +}; \ No newline at end of file diff --git a/lib/core/parser.js b/lib/core/parser.js new file mode 100644 index 0000000..73091dc --- /dev/null +++ b/lib/core/parser.js @@ -0,0 +1,752 @@ +const fs = require('fs-extra'); +const path = require('path'); +const { randomUUID } = require('crypto'); +const { v4: uuidv4 } = require('uuid'); + +const NOOP = function() {}; + +const OPERATORS = { + '{' : 'L_CURLY', + '}' : 'R_CURLY', + '(' : 'L_PAREN', + ')' : 'R_PAREN', + '[' : 'L_BRACKET', + ']' : 'R_BRACKET', + '.' : 'PERIOD', + ',' : 'COMMA', + ':' : 'COLON', + '?' : 'QUESTION', + // Arithmetic operators + '+' : 'ADDICTIVE', + '-' : 'ADDICTIVE', + '*' : 'MULTIPLICATIVE', + '/' : 'MULTIPLICATIVE', + '%' : 'MULTIPLICATIVE', + // Comparison operators + '===': 'EQUALITY', + '!==': 'EQUALITY', + '==' : 'EQUALITY', + '!=' : 'EQUALITY', + '<' : 'RELATIONAL', + '>' : 'RELATIONAL', + '<=' : 'RELATIONAL', + '>=' : 'RELATIONAL', + 'in' : 'RELATIONAL', + // Logical operators + '&&' : 'LOGICAL_AND', + '||' : 'LOGICAL_OR', + '!' : 'LOGICAL_NOT', + // Bitwise operators + '&' : 'BITWISE_AND', + '|' : 'BITWISE_OR', + '^' : 'BITWISE_XOR', + '~' : 'BITWISE_NOT', + '<<' : 'BITWISE_SHIFT', + '>>' : 'BITWISE_SHIFT', + '>>>': 'BITWISE_SHIFT' +}; + +const EXPRESSIONS = { + 'in' : function(a, b) { return a() in b(); }, + '?' : function(a, b, c) { return a() ? b() : c(); }, + '+' : function(a, b) { a = a(); b = b(); return a == null ? b : b == null ? a : a + b; }, + '-' : function(a, b) { return a() - b(); }, + '*' : function(a, b) { return a() * b(); }, + '/' : function(a, b) { return a() / b(); }, + '%' : function(a, b) { return a() % b(); }, + '===': function(a, b) { return a() === b(); }, + '!==': function(a, b) { return a() !== b(); }, + '==' : function(a, b) { return a() == b(); }, + '!=' : function(a, b) { return a() != b(); }, + '<' : function(a, b) { return a() < b(); }, + '>' : function(a, b) { return a() > b(); }, + '<=' : function(a, b) { return a() <= b(); }, + '>=' : function(a, b) { return a() >= b(); }, + '&&' : function(a, b) { return a() && b(); }, + '||' : function(a, b) { return a() || b(); }, + '&' : function(a, b) { return a() & b(); }, + '|' : function(a, b) { return a() | b(); }, + '^' : function(a, b) { return a() ^ b(); }, + '<<' : function(a, b) { return a() << b(); }, + '>>' : function(a, b) { return a() >> b(); }, + '>>>': function(a, b) { return a() >>> b(); }, + '~' : function(a) { return ~a(); }, + '!' : function(a) { return !a(); } +}; + +const ESCAPE = { + 'n': '\n', + 'f': '\f', + 'r': '\r', + 't': '\t', + 'v': '\v', + "'": "'", + '"': '"', + '`': '`' +}; + +const formatters = require('../formatters'); + +// User formatters +if (fs.existsSync('extensions/server_connect/formatters')) { + const files = fs.readdirSync('extensions/server_connect/formatters'); + for (let file of files) { + if (path.extname(file) == '.js') { + Object.assign(formatters, require(`../../extensions/server_connect/formatters/${file}`)); + } + } +} + +function lexer(expr) { + let tokens = [], + token, + name, + start, + index = 0, + op = true, + ch, + ch2, + ch3; + + while (index < expr.length) { + start = index; + + ch = read(); + + if (isQuote(ch) && op) { + name = 'STRING'; + token = readString(ch); + op = false; + } else if ((isDigid(ch) || (is('.') && peek() && isDigid(peek()))) && op) { + name = 'NUMBER'; + token = readNumber(); + op = false; + } else if (isAlpha(ch) && op) { + name = 'IDENT'; + token = readIdent(); + if (is('(')) { + name = 'METHOD'; + } + op = false; + } else if (is('/') && op && (token == '(' || token == ',' || token == '?' || token == ':') && testRegexp()) { + name = 'REGEXP'; + token = readRegexp(); + op = false; + } else if (isWhitespace(ch)) { + index++; + continue; + } else if ((ch3 = read(3)) && OPERATORS[ch3]) { + name = OPERATORS[ch3]; + token = ch3; + op = true; + index += 3; + } else if ((ch2 = read(2)) && OPERATORS[ch2]) { + name = OPERATORS[ch2]; + token = ch2; + op = true; + index += 2; + } else if (OPERATORS[ch]) { + name = OPERATORS[ch]; + token = ch; + op = true; + index++; + } else { + throw new Error('Lexer Error: Unexpected token "' + ch + '" at column ' + index + ' in expression {{' + expr + '}}'); + } + + tokens.push({ + name: name, + index: start, + value: token + }); + } + + return tokens; + + function read(n) { + if (!n) n = 1; + return expr.substr(index, n); + } + + function peek(n) { + n = n || 1; + return index + n < expr.length ? expr[index + n] : false; + } + + function is(chars) { + return chars.indexOf(ch) != -1; + } + + function isQuote(ch) { + return ch == '"' || ch == "'"; + } + + function isDigid(ch) { + return ch >= '0' && ch <= '9'; + } + + function isAlpha(ch) { + return (ch >= 'a' && ch <= 'z') || + (ch >= 'A' && ch <= 'Z') || + ch == '_' || ch == '$'; + } + + function isAlphaNum(ch) { + return isAlpha(ch) || isDigid(ch); + } + + function isWhitespace(ch) { + return ch == ' ' || ch == '\r' || ch == '\t' || ch == '\n' || ch == '\v' || ch == '\u00A0'; + } + + function isExpOperator(ch) { + return ch == '-' || ch == '+' || isDigid(ch); + } + + function readString(quote) { + let str = '', esc = false; + + index++; + + while (index < expr.length) { + ch = read(); + + if (esc) { + if (ch == 'u') { + // unicode escape + index++; + let hex = read(4); + if (!hex.match(/[\da-f]{4}/i)) { + throw new Error('Lexer Error: Invalid unicode escape at column ' + index + ' in expression {{' + expr + '}}'); + } + str += String.fromCharCode(parseInt(hex, 16)); + index += 3; + } else { + str += ESCAPE[ch] ? ESCAPE[ch] : ch; + } + + esc = false; + } else if (ch == '\\') { + // escape character + esc = true; + } else if (ch == quote) { + // end of string + index ++; + return str; + } else { + str += ch; + } + + index++; + } + + throw new Error('Lexer Error: Unterminated string at column ' + index + ' in expression {{' + expr + '}}'); + } + + function readNumber() { + let num = '', exp = false; + + while (index < expr.length) { + ch = read(); + + if (isDigid(ch) || (is('.') && peek() && isDigid(peek()))) { + num += ch; + } else { + let next = peek(); + + if (is('eE') && isExpOperator(next)) { + num += 'e'; + exp = true; + } else if (isExpOperator(ch) && next && isDigid(next) && exp) { + num += ch; + exp = false; + } else if (isExpOperator(ch) && (!next || !isDigid(next)) && exp) { + throw new Error('Lexer Error: Invalid exponent at column ' + index + ' in expression {{' + expr + '}}'); + } else { + break; + } + } + + index++; + } + + return +num; + } + + function readIdent() { + let ident = ''; + + while (index < expr.length) { + ch = read(); + + if (isAlphaNum(ch)) { + ident += ch; + } else { + break; + } + + index++; + } + + return ident; + } + + function readRegexp() { + let re = '', mod = '', esc = false; + + index ++; + + while (index < expr.length) { + ch = read(); + + if (esc) { + esc = false; + } else if (ch == '\\') { + esc = true; + } else if (ch == '/') { + index++; + + while ('ign'.indexOf(ch = read()) != -1) { + mod += ch; + index++; + } + + return re + '%%%' + mod; + } + + re += ch; + index++; + } + + throw new Error('Lexer Error: Unterminated regexp at column ' + index + ' in expression {{' + expr + '}}'); + } + + function testRegexp() { + var idx = index, ok = true; + + try { + readRegexp(); + } catch (e) { + ok = false; + } + + // reset our index and ch + index = idx; + ch = '/'; + + return ok; + } +} + +function parser(expr, scope) { + let tokens = lexer(expr), + context = undefined, + RESERVED = { + 'PI' : function() { return Math.PI; }, + 'UUID' : function() { return randomUUID ? randomUUID() : uuidv4(); }, + 'NOW' : function() { return date(); }, + 'NOW_UTC' : function() { return utc_date(); }, + 'TIMESTAMP': function() { return timestamp(); }, + '$this' : function() { return scope.data; }, + '$global' : function() { return globalScope.data; }, + '$parent' : function() { return scope.parent && scope.parent.data; }, + 'null' : function() { return null; }, + 'true' : function() { return true; }, + 'false' : function() { return false; }, + '_' : function() { return { __dmxScope__: true } } + }; + + return start()(); + + function pad(s, n) { + return ('000' + s).substr(-n); + } + + function date(dt) { + dt = dt || new Date(); + return pad(dt.getFullYear(), 4) + '-' + pad(dt.getMonth() + 1, 2) + '-' + pad(dt.getDate(), 2) + ' ' + + pad(dt.getHours(), 2) + ':' + pad(dt.getMinutes(), 2) + ':' + pad(dt.getSeconds(), 2); + } + + function utc_date(dt) { + dt = dt || new Date(); + return pad(dt.getUTCFullYear(), 4) + '-' + pad(dt.getUTCMonth() + 1, 2) + '-' + pad(dt.getUTCDate(), 2) + 'T' + + pad(dt.getUTCHours(), 2) + ':' + pad(dt.getUTCMinutes(), 2) + ':' + pad(dt.getUTCSeconds(), 2) + 'Z'; + } + + function timestamp(dt) { + dt = dt || new Date(); + return ~~(dt / 1000); + } + + function read() { + if (tokens.length === 0) { + throw new Error('Parser Error: Unexpected end of expression {{' + expr + '}}'); + } + + return tokens[0]; + } + + function peek(e) { + if (tokens.length > 0) { + let token = tokens[0]; + + if (!e || token.name == e) { + return token; + } + } + + return false; + } + + function expect(e) { + let token = peek(e); + + if (token) { + tokens.shift(); + return token; + } + + return false; + } + + function consume(e) { + if (!expect(e)) { + throw new Error('Parser Error: Unexpected token, expecting ' + e + ' in expression {{' + expr + '}}'); + } + } + + function fn(expr) { + let args = [].slice.call(arguments, 1); + + return function() { + if (EXPRESSIONS[expr]) { + return EXPRESSIONS[expr].apply(context, args); + } else { + return expr; + } + } + } + + function start() { + return conditional(); + } + + function conditional() { + let left = logicalOr(), middle, token; + + if ((token = expect('QUESTION'))) { + middle = conditional(); + + if ((token = expect('COLON'))) { + return fn('?', left, middle, conditional()); + } else { + throw new Error('Parse Error: Expecting : in expression {{' + expr + '}}'); + } + } else { + return left; + } + } + + function logicalOr() { + let left = logicalAnd(), token; + + while (true) { + if ((token = expect('LOGICAL_OR'))) { + left = fn(token.value, left, logicalAnd()); + } else { + return left; + } + } + } + + function logicalAnd() { + let left = bitwiseOr(), token; + + if ((token = expect('LOGICAL_AND'))) { + left = fn(token.value, left, logicalAnd()); + } + + return left; + } + + function bitwiseOr() { + let left = bitwiseXor(), token; + + if ((token = expect('BITWISE_OR'))) { + left = fn(token.value, left, bitwiseXor()); + } + + return left; + } + + function bitwiseXor() { + let left = bitwiseAnd(), token; + + if ((token = expect('BITWISE_XOR'))) { + left = fn(token.value, left, bitwiseAnd()); + } + + return left; + } + + function bitwiseAnd() { + let left = equality(), token; + + if ((token = expect('BITWISE_AND'))) { + left = fn(token.value, left, bitwiseAnd()); + } + + return left; + } + + function equality() { + let left = relational(), token; + + if ((token = expect('EQUALITY'))) { + left = fn(token.value, left, equality()); + } + + return left; + } + + function relational() { + let left = bitwiseShift(), token; + + if ((token = expect('RELATIONAL'))) { + left = fn(token.value, left, relational()); + } + + return left; + } + + function bitwiseShift() { + let left = addictive(), token; + + if ((token = expect('BITWISE_SHIFT'))) { + left = fn(token.value, left, addictive()); + } + + return left; + } + + function addictive() { + let left = multiplicative(), token; + + while ((token = expect('ADDICTIVE'))) { + left = fn(token.value, left, multiplicative()); + } + + return left; + } + + function multiplicative() { + let left = unary(), token; + + while ((token = expect('MULTIPLICATIVE'))) { + left = fn(token.value, left, unary()); + } + + return left; + } + + function unary() { + let token; + + if ((token = expect('ADDICTIVE'))) { + if (token.value == '+') { + return primary(); + } else { + return fn(token.value, function() { return 0; }, unary()); + } + } else if ((token = expect('LOGICAL_NOT'))) { + return fn(token.value, unary()); + } + + return primary(); + } + + function primary() { + let value, next; + + if (expect('L_PAREN')) { + value = start(); + consume('R_PAREN'); + } else if (expect('L_CURLY')) { + let obj = {}; + + if (read().name != 'R_CURLY') { + do { + let key = expect().value; + consume('COLON'); + obj[key] = start()(); + } while (expect('COMMA')); + } + + value = fn(obj); + + consume('R_CURLY'); + } else if (expect('L_BRACKET')) { + let arr = []; + + if (read().name != 'R_BRACKET') { + do { + arr.push(start()()); + } while (expect('COMMA')); + } + + value = fn(arr); + + consume('R_BRACKET'); + } else if (expect('PERIOD')) { + value = peek() ? objectMember(fn(scope.data)) : fn(scope.data); + } else { + let token = expect(); + + if (token === false) { + throw new Error('Parser Error: Not a primary expression {{' + expr + '}}'); + } + + if (token.name == 'IDENT') { + value = RESERVED.hasOwnProperty(token.value) + ? RESERVED[token.value] + : function() { return scope.get(token.value) }; + } else if (token.name == 'METHOD') { + if (!formatters[token.value]) { + throw new Error('Parser Error: Formatter "' + token.value + '" does not exist, expression {{' + expression + '}}'); + } + + value = fn(formatters[token.value]); + } else if (token.name == 'REGEXP') { + value = function() { + let re = token.value.split('%%%'); + return new RegExp(re[0], re[1]); + }; + } else { + value = function() { return token.value }; + } + } + + while ((next = expect('L_PAREN') || expect('L_BRACKET') || expect('PERIOD'))) { + if (next.value == '(') { + value = functionCall(value, context); + } else if (next.value == '[') { + value = objectIndex(value); + } else if (next.value == '.') { + context = value; + value = objectMember(value); + } else { + throw new Error('Parser Error: Parse error in expression {{' + expr + '}}'); + } + } + + context = undefined; + + return value; + } + + function functionCall(func, ctx) { + let argsFn = []; + + if (read().name != 'R_PAREN') { + do { + argsFn.push(start()); + } while (expect('COMMA')); + } + + consume('R_PAREN'); + + return function() { + let args = []; + + if (ctx) args.push(ctx()); + + for (let argFn of argsFn) { + args.push(argFn()); + } + + let fnPtr = func() || NOOP; + + return fnPtr.apply(null, args); + } + } + + function objectIndex(obj) { + let indexFn = start(); + + consume('R_BRACKET'); + + return function() { + let o = obj(), + i = indexFn(); + + if (typeof o != 'object') return undefined; + + if (o.__dmxScope__) { + return scope.get(i); + } + + return o[i]; + } + } + + function objectMember(obj) { + let token = expect(); + + return function() { + let o = obj(); + + if (token.name == 'METHOD') { + if (!formatters[token.value]) { + throw new Error('Parser Error: Formatter "' + token.value + '" does not exist, expression {{' + expr + '}}'); + } + + return formatters[token.value]; + } + + if (o && o.__dmxScope) { + return scope.get(token.value); + } + + return o && o[token.value]; + } + } +} + +function parseValue(value, scope) { + if (value == null) return value; + + value = value.valueOf(); + + if (typeof value == 'object') { + for (let key in value) { + value[key] = parseValue(value[key], scope); + } + } + + if (typeof value == 'string') { + if (value.substr(0, 2) == '{{' && value.substr(-2) == '}}') { + let expr = value.replace(/^\{\{|\}\}$/g, ''); + + if (expr.indexOf('{{') == -1) { + return parser(expr, scope); + } + } + + return parseTemplate(value, scope); + } + + return value; +} + +function parseTemplate(template, scope) { + return template.replace(/\{\{(.*?)\}\}/g, function(a, m) { + var value = parser(m, scope); + return value != null ? String(value) : ''; + }); +} + +exports.lexer = lexer; +exports.parse = parser; +exports.parseValue = parseValue; +exports.parseTemplate = parseTemplate; \ No newline at end of file diff --git a/lib/core/path.js b/lib/core/path.js new file mode 100644 index 0000000..2166a34 --- /dev/null +++ b/lib/core/path.js @@ -0,0 +1,97 @@ +const { existsSync: exists } = require('fs'); +const { dirname, basename, extname, join, resolve, relative, posix } = require('path'); +const { v4: uuidv4 } = require('uuid'); +const debug = require('debug')('server-connect:path'); + +module.exports = { + + getFilesArray: function(paths) { + let files = []; + + if (!Array.isArray(paths)) { + paths = [paths]; + } + + for (let path of paths) { + if (Array.isArray(path)) { + files = files.concat(module.exports.getFilesArray(path)); + } else if (path && path.path) { + files.push(module.exports.toSystemPath(path.path)); + } else if (path) { + files.push(module.exports.toSystemPath(path)); + } + } + + return files; + }, + + toSystemPath: function(path) { + if (path[0] != '/' || path.includes('../')) { + throw new Error(`path.toSystemPath: Invalid path "${path}".`); + } + + return resolve('.' + path); + }, + + toAppPath: function(path) { + let root = resolve('.'); + let rel = relative(root, path).replace(/\\/g, '/'); + + debug('toAppPath: %O', { root, path, rel }); + + if (rel.includes('../')) { + throw new Error(`path.toAppPath: Invalid path "${rel}".`); + } + + return '/' + rel; + }, + + toSiteUrl: function(path) { + let root = resolve('public'); + let rel = relative(root, path).replace(/\\/g, '/'); + + debug('toSiteUrl: %O', { root, path, rel }); + + if (rel.includes('../')) { + return ''; + } + + return '/' + rel; + }, + + getUniqFile: function(path) { + let n = 1; + + while (exists(path)) { + path = path.replace(/(_(\d+))?(\.\w+)$/, (a, b, c, d) => '_' + (n++) + (d || a)); + if (n > 999) throw new Error(`path.getUniqFile: Couldn't create a unique filename for ${path}`); + } + + return path; + }, + + parseTemplate: function(path, template) { + let n = 1, dir = dirname(path), file = template.replace(/\{([^\}]+)\}/g, (a, b) => { + switch (b) { + case 'name': return basename(path, extname(path)); + case 'ext' : return extname(path); + case 'guid': return uuidv4(); + } + + return a; + }); + + if (file.includes('{_n}')) { + template = file; + file = template.replace('{_n}', ''); + + while (exists(join(dir, file))) { + file = template.replace('{_n}', n++); + if (n > 999) throw new Error(`path.parseTemplate: Couldn't create a unique filename for ${path}`); + } + } + + return join(dir, file); + } + +}; \ No newline at end of file diff --git a/lib/core/scope.js b/lib/core/scope.js new file mode 100644 index 0000000..dc8d92e --- /dev/null +++ b/lib/core/scope.js @@ -0,0 +1,63 @@ +function Scope(data, parent, context) { + if (typeof data != 'object') { + data = { $value: data }; + } + + this.data = data; + this.parent = parent; + this.context = context; +}; + +Scope.prototype = { + create: function(data, context) { + return new Scope(data, this, context); + }, + + get: function(name) { + if (this.data[name] !== undefined) { + return this.data[name]; + } + + if (this.parent) { + return this.parent.get(name); + } + + return undefined; + }, + + set: function(name, value) { + if (typeof name == 'object') { + for (let prop in name) { + this.set(prop, name[prop]); + } + } else { + this.data[name] = value; + + if (this.context) { + this.context[name] = value; + } + } + }, + + has: function(name) { + if (this.data[name] !== undefined) { + return true; + } + + if (this.parent) { + return this.parent.has(name); + } + + return false; + }, + + remove: function(name) { + delete this.data[name]; + + if (this.context) { + delete this.context[name]; + } + }, +} + +module.exports = Scope; \ No newline at end of file diff --git a/lib/core/util.js b/lib/core/util.js new file mode 100644 index 0000000..9b3e59e --- /dev/null +++ b/lib/core/util.js @@ -0,0 +1,185 @@ +const reGrouping = /(\d+)(\d{3})/; + +function clone(o) { + if (o == null) { + return o; + } + + if (Array.isArray(o)) { + return o.map(clone); + } + + if (o instanceof Date) { + // we do not clone date objects but convert them to an iso string + return o.toISOString(); + } + + if (typeof o == 'object') { + let oo = {}; + for (let key in o) { + oo[key] = clone(o[key]); + } + return oo; + } + + return o; +} + +function mergeDeep(target, source) { + if (isObject(target) && isObject(source)) { + for (const key in source) { + if (isObject(source[key])) { + if (!target[key]) { + Object.assign(target, { [key]: {} }); + } + + mergeDeep(target[key], source[key]); + } else { + Object.assign(target, { [key]: source[key] }); + } + } + } + + return target; +} + +function isObject(item) { + return (item && typeof item === 'object' && !Array.isArray(item) && item !== null); + } + +module.exports = { + + clone, + + mergeDeep, + + escapeRegExp: function(val) { + // https://github.com/benjamingr/RegExp.escape + return val.replace(/[\\^$*+?.()|[\]{}]/g, '\\$&'); + }, + + keysToLowerCase: function(o) { + return Object.keys(o).reduce((c, k) => (c[k.toLowerCase()] = o[k], c), {}); + }, + + padNumber: function(num, digids, trim) { + let sign = ''; + + if (num < 0) { + sign = '-'; + num = -num; + } + + num = String(num); + + while (num.length < digids) { + num = '0' + num; + } + + if (trim) { + num = num.substr(num.length - digids); + } + + return sign + num; + }, + + formatNumber: function(num, decimals, decimalSeparator, groupingSeparator) { + num = Number(num); + + if (isNaN(num) || !isFinite(num)) return ''; + + decimalSeparator = typeof decimalSeparator == 'string' ? decimalSeparator : '.'; + groupingSeparator = typeof groupingSeparator == 'string' ? groupingSeparator : ''; + precision = typeof precision == 'number' ? Math.abs(precision) : null; + + let minus = num < 0; + let parts = (decimals == null ? String(Math.abs(num)) : Math.abs(num).toFixed(decimals)).split('.'); + let wholePart = parts[0]; + let decimalPart = parts.length > 1 ? decimalSeparator + parts[1] : ''; + + if (groupingSeparator) { + while (reGrouping.test(wholePart)) { + wholePart = wholePart.replace(reGrouping, '$1' + groupingSeparator + '$2'); + } + } + + return (minus ? '-' : '') + wholePart + decimalPart; + }, + + parseDate: function(obj) { + if (Object.prototype.toString.call(obj) == '[object Date]') return new Date(obj); + + let date = new Date(obj); + + if (typeof obj == 'number') { + date = new Date(obj * 1000); + } + + if (typeof obj == 'string') { + if (obj == 'now') { + date = new Date(); + } else if (/^\d{4}-\d{2}-\d{2}$/.test(obj)) { + let parts = obj.split('-'); + date = new Date(parts[0], parts[1] - 1, parts[2]); + } else if (/^\d{2}:\d{2}:\d{2}$/.test(obj)) { + let parts = obj.split(':'); + date = new Date(); + date.setHours(parts[0]); + date.setMinutes(parts[1]); + date.setSeconds(parts[2]); + date.setMilliseconds(0); + } + } + + if (date.toString() == 'Invalid Date') { + return undefined; + } + + return date; + }, + + formatDate: function(date, format, utc, locale) { + date = module.exports.parseDate(date); + if (date == null) return date; + locale = typeof locale == 'string' ? locale : 'en-US'; + const lang = require('../locale/' + locale); + const pad = (v, n) => `0000${v}`.substr(-n); + + let y = utc ? date.getUTCFullYear() : date.getFullYear(), + n = utc ? date.getUTCMonth() : date.getMonth(), + d = utc ? date.getUTCDate() : date.getDate(), + w = utc ? date.getUTCDay() : date.getDay(), + h = utc ? date.getUTCHours() : date.getHours(), + m = utc ? date.getUTCMinutes() : date.getMinutes(), + s = utc ? date.getUTCSeconds() : date.getSeconds(), + v = utc ? date.getUTCMilliseconds() : date.getMilliseconds(); + + return format.replace(/([yMdHhmsaAv])(\1+)?/g, part => { + switch (part) { + case 'yyyy': return pad(y, 4); + case 'yy': return pad(y, 2); + case 'y': return y; + case 'MMMM': return lang.months[n]; + case 'MMM': return lang.monthsShort[n]; + case 'MM': return pad(n + 1, 2); + case 'M': return n + 1; + case 'dddd': return lang.days[w]; + case 'ddd': return lang.daysShort[w]; + case 'dd': return pad(d, 2); + case 'd': return d; + case 'HH': return pad(h, 2); + case 'H': return h; + case 'hh': return pad((h % 12) || 12, 2); + case 'h': return (h % 12) || 12; + case 'mm': return pad(m, 2); + case 'm': return m; + case 'ss': return pad(s, 2); + case 's': return s; + case 'a': return h < 12 ? 'am' : 'pm'; + case 'A': return h < 12 ? 'AM' : 'PM'; + case 'v': return pad(v, 3); + } + }); + } + +}; \ No newline at end of file diff --git a/lib/core/webhook.js b/lib/core/webhook.js new file mode 100644 index 0000000..f6ac8f1 --- /dev/null +++ b/lib/core/webhook.js @@ -0,0 +1,24 @@ +// Helper methods for webhooks + +const fs = require('fs-extra'); +const App = require('./app'); + +exports.createHandler = function(name, fn = () => 'handler') { + return async (req, res, next) => { + const action = await fn(req, res, next); + + if (typeof action == 'string') { + const path = `app/webhooks/${name}/${action}.json`; + + if (fs.existsSync(path)) { + const app = new App(req, res); + let json = await fs.readJSON(path); + return Promise.resolve(app.define(json)).catch(next); + } else { + res.json({error: `No action found for ${action}.`}); + // do not return 404 else stripe will retry + //next(); + } + } + } +}; \ No newline at end of file diff --git a/lib/formatters/collections.js b/lib/formatters/collections.js new file mode 100644 index 0000000..f7dd2a0 --- /dev/null +++ b/lib/formatters/collections.js @@ -0,0 +1,169 @@ +module.exports = { + + join: function(arr, separator, prop) { + if (!Array.isArray(arr)) return arr; + return arr.map(item => prop ? item[prop] : item).join(separator); + }, + + first: function(arr) { + if (!Array.isArray(arr)) return arr; + return arr.length ? arr[0] : null; + }, + + top: function(arr, count) { + if (!Array.isArray(arr)) return arr; + return arr.slice(0, count); + }, + + last: function(arr, count) { + if (!Array.isArray(arr)) return arr; + if (count) return arr.slice(-count); + return arr[arr.length - 1]; + }, + + where: function(arr, prop, operator, value) { + if (!Array.isArray(arr)) return arr; + return arr.filter(item => { + let val = item[prop]; + + switch (operator) { + case 'startsWith': return String(val).startsWith(value); + case 'endsWith': return String(val).endsWith(value); + case 'contains': return String(val).includes(value); + case '===': return val === value; + case '!==': return val !== value; + case '==': return val == value; + case '!=': return val != value; + case '<=': return val <= value; + case '>=': return val >= value; + case '<': return val < value; + case '>': return val > value; + }; + + return true; + }); + }, + + unique: function(arr, prop) { + if (!Array.isArray(arr)) return arr; + if (prop) arr = arr.map(item => item[prop]); + + let lookup = []; + return arr.filter(item => { + if (lookup.includes(item)) return false; + lookup.push(item); + return true; + }); + }, + + groupBy: function(arr, prop) { + if (!Array.isArray(arr)) return arr; + return arr.reduce((groups, item) => { + let group = String(item[prop]); + if (!groups[group]) groups[group] = []; + groups[group].push(item); + return groups; + }, {}); + }, + + sort: function(arr, prop) { + if (!Array.isArray(arr)) return arr; + return arr.slice(0).sort((a, b) => { + a = prop ? a && a[prop] : a; + b = prop ? b && b[prop] : b; + + if (typeof a == 'string' && typeof b == 'string') { + return a.localeCompare(b); + } + + return a < b ? -1 : a > b ? 1 : 0; + }); + }, + + randomize: function(arr) { + if (!Array.isArray(arr)) return arr; + arr = arr.slice(0); + let len = arr.length; + if (len) { + while (--len) { + let rnd = Math.floor(Math.random() * (len + 1)); + let val = arr[len]; + arr[len] = arr[rnd]; + arr[rnd] = val; + } + } + return arr; + }, + + reverse: function(arr) { + if (!Array.isArray(arr)) return arr; + return arr.slice(0).reverse(); + }, + + count: function(arr) { + if (!Array.isArray(arr)) return arr; + return arr.length; + }, + + min: function(arr, prop) { + if (!Array.isArray(arr)) return arr; + return arr.reduce((min, num) => { + num = prop ? num[prop] : num; + if (num == null) return min; + num = Number(num); + if (isNaN(num) || !isFinite(num)) return min; + return min == null || num < min ? num : min; + }, null); + }, + + max: function(arr, prop) { + if (!Array.isArray(arr)) return arr; + return arr.reduce((max, num) => { + num = prop ? num[prop] : num; + if (num == null) return max; + num = Number(num); + if (isNaN(num) || !isFinite(num)) return max; + return max == null || num > max ? num : max; + }, null); + }, + + sum: function(arr, prop) { + if (!Array.isArray(arr)) return arr; + return arr.reduce((sum, num) => { + num = prop ? num[prop] : num; + if (num == null) return sum; + num = Number(num); + if (isNaN(num) || !isFinite(num)) return sum; + return sum + num; + }, 0); + }, + + avg: function(arr, prop) { + if (!Array.isArray(arr)) return arr; + let cnt = 0; + return arr.reduce((avg, num) => { + num = prop ? num[prop] : num; + if (num == null) return avg; + num = Number(num); + if (isNaN(num) || !isFinite(num)) return avg; + cnt++; + return avg + num; + }, 0) / cnt; + }, + + flatten: function(arr, prop) { + if (!Array.isArray(arr)) return arr; + return arr.map(item => item[prop]); + }, + + keys: function(obj) { + if (typeof obj != 'object') return obj; + return Object.keys(obj); + }, + + values: function(obj) { + if (typeof obj != 'object') return obj; + return Object.values(obj); + }, + +}; \ No newline at end of file diff --git a/lib/formatters/conditional.js b/lib/formatters/conditional.js new file mode 100644 index 0000000..9ef50d0 --- /dev/null +++ b/lib/formatters/conditional.js @@ -0,0 +1,30 @@ +module.exports = { + + startsWith: function(val, str) { + if (val == null) return false; + return String(val).startsWith(str); + }, + + endsWith: function(val, str) { + if (val == null) return false; + return String(val).endsWith(str); + }, + + contains: function(val, str) { + if (val == null) return false; + return String(val).includes(str); + }, + + between: function(val, min, max) { + return val >= min && val <= max; + }, + + inRange: function(val, min, max) { + val = Number(val); + min = Number(min); + max = Number(max); + + return val >= min && val <= max; + }, + +}; \ No newline at end of file diff --git a/lib/formatters/core.js b/lib/formatters/core.js new file mode 100644 index 0000000..23e1755 --- /dev/null +++ b/lib/formatters/core.js @@ -0,0 +1,27 @@ +module.exports = { + + default: function(val, def) { + return val ? val : def; + }, + + then: function(val, trueVal, falseVal) { + return val ? trueVal : falseVal; + }, + + toNumber: function(val) { + return Number(val); + }, + + toString: function(val) { + return String(val); + }, + + toJSON: function(val) { + return JSON.stringify(val); + }, + + parseJSON: function(val) { + return JSON.parse(val); + }, + +}; \ No newline at end of file diff --git a/lib/formatters/crypto.js b/lib/formatters/crypto.js new file mode 100644 index 0000000..b6673da --- /dev/null +++ b/lib/formatters/crypto.js @@ -0,0 +1,99 @@ +const { createHash, createHmac, createCipheriv, createDecipheriv, randomBytes, scryptSync, randomUUID } = require('crypto'); +const { v4: uuidv4 } = require('uuid'); + +module.exports = { + + randomUUID: function() { + return randomUUID ? randomUUID() : uuidv4(); + }, + + md5: function(data, salt, enc) { + return hash('md5', data, salt, enc) + }, + + sha1: function(data, salt, enc) { + return hash('sha1', data, salt, enc) + }, + + sha256: function(data, salt, enc) { + return hash('sha256', data, salt, enc) + }, + + sha512: function(data, salt, enc) { + return hash('sha512', data, salt, enc) + }, + + hash: function(data, alg, enc) { + return hash(alg, data, '', enc); + }, + + hmac: function(data, alg, secret, enc) { + return hmac(alg, data, secret, enc); + }, + + transform: function(data, from, to) { + return transform(data, from, to); + }, + + encodeBase64: function(data, enc) { + enc = typeof enc == 'string' ? enc : 'utf8'; + return transform(data, enc, 'base64'); + }, + + decodeBase64: function(data, enc) { + enc = typeof enc == 'string' ? enc : 'utf8'; + return transform(data, 'base64', enc); + }, + + encodeBase64Url: function(data, enc) { + enc = typeof enc == 'string' ? enc : 'utf8'; + return transform(data, enc, 'base64').replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); + }, + + decodeBase64Url: function(data, enc) { + enc = typeof enc == 'string' ? enc : 'utf8'; + return transform(data.replace(/-/g, '+').replace(/_/g, '/'), 'base64', enc); + }, + + encrypt: function(data, password, enc) { + const config = require('../setup/config'); + password = typeof password == 'string' ? password : config.secret; + enc = typeof enc == 'string' ? enc : 'base64'; + const iv = randomBytes(16); + const key = scryptSync(password, iv, 32); + const cipher = createCipheriv('aes-256-cbc', key, iv); + const encrypted = cipher.update(data, 'utf8', enc); + return iv.toString(enc) + '.' + encrypted + cipher.final(enc); + }, + + decrypt: function(data, password, enc) { + const config = require('../setup/config'); + password = typeof password == 'string' ? password : config.secret; + enc = typeof enc == 'string' ? enc : 'base64'; + const iv = Buffer.from(data.split('.')[0], enc); + const key = scryptSync(password, iv, 32); + const decipher = createDecipheriv('aes-256-cbc', key, iv); + const decrypted = decipher.update(data.split('.')[1], enc, 'utf8'); + return decrypted + decipher.final('utf8'); + }, + +}; + +function hash(alg, data, salt, enc) { + if (data == null) return data; + salt = typeof salt == 'string' ? salt : ''; + enc = typeof enc == 'string' ? enc : 'hex'; + return createHash(alg).update(data + salt).digest(enc); +} + +function hmac(alg, data, secret, enc) { + if (data == null) return data; + secret = typeof secret == 'string' ? secret : ''; + enc = typeof enc == 'string' ? enc : 'hex'; + return createHmac(alg, secret).update(data).digest(enc); +} + +function transform(data, from, to) { + if (data == null) return data; + return Buffer.from(String(data), from).toString(to); +} \ No newline at end of file diff --git a/lib/formatters/date.js b/lib/formatters/date.js new file mode 100644 index 0000000..f7c8c97 --- /dev/null +++ b/lib/formatters/date.js @@ -0,0 +1,83 @@ +const { padNumber, parseDate, formatDate } = require('../core/util'); + +module.exports = { + + formatDate: function(date, format, utc) { + date = parseDate(date); + if (date == null) return date; + + return formatDate(date, format, utc); + }, + + dateAdd: function(date, interval, num) { + date = parseDate(date); + if (date == null) return date; + + switch (interval) { + case 'years': date.setFullYear(date.getFullYear() + num); break; + case 'months': date.setMonth(date.getMonth() + num); break; + case 'weeks': date.setDate(date.getDate() + (num * 7)); break; + case 'days': date.setDate(date.getDate() + num); break; + case 'hours': date.setHours(date.getHours() + num); break; + case 'minutes': date.setMinutes(date.getMinutes() + num); break; + case 'seconds': date.setSeconds(date.getSeconds() + num); break; + } + + return date.toISOString(); + }, + + dateDiff: function(date, interval, date2) { + date = parseDate(date); + date2 = parseDate(date2); + + let diff = Math.abs(date2 - date); + + if (isNaN(diff) || date == null || date2 == null) return undefined; + + let s = 1000, m = s * 60, h = m * 60, d = h * 24, w = d * 7; + + switch (interval) { + case 'years': return Math.abs(date2.getFullYear() - date.getFullYear()); + case 'months': return Math.abs((date2.getFullYear() * 12 + date2.getMonth()) - (date.getFullYear() * 12 + date.getMonth())); + case 'weeks': return Math.floor(diff / w); + case 'days': return Math.floor(diff / d); + case 'hours': return Math.floor(diff/ h); + case 'minutes': return Math.floor(diff / m); + case 'seconds': return Math.floor(diff / s); + case 'hours:minutes': + h = Math.floor(diff / h); + m = Math.floor(diff / m); + return padNumber(h, 2) + ':' + padNumber(m - (h * 60), 2); + case 'minutes:seconds': + m = Math.floor(diff / m); + s = Math.floor(diff / s); + return padNumber(m, 2) + ':' + padNumber(s - (m * 60), 2); + case 'hours:minutes:seconds': + h = Math.floor(diff / h); + m = Math.floor(diff / m); + s = Math.floor(diff / s); + return padNumber(h, 2) + ':' + padNumber(m - (h * 60), 2) + ':' + padNumber(s - m * 60, 2); + } + + return undefined; + }, + + toTimestamp: function(date) { + date = parseDate(date); + if (date == null) return date; + return Math.floor(date.getTime() / 1000); + }, + + toLocalTime: function(date) { + date = parseDate(date); + if (date == null) return date; + return formatDate(date, 'yyyy-MM-dd HH:mm:ss.v'); + }, + + toUTCTime: function(date) { + date = parseDate(date); + if (date == null) return date; + return date.toISOString(); + }, + +}; \ No newline at end of file diff --git a/lib/formatters/index.js b/lib/formatters/index.js new file mode 100644 index 0000000..316458c --- /dev/null +++ b/lib/formatters/index.js @@ -0,0 +1,17 @@ +const collections = require('./collections'); +const conditional = require('./conditional'); +const core = require('./core'); +const crypto = require('./crypto'); +const date = require('./date'); +const number = require('./number'); +const string = require('./string'); + +module.exports = { + ...collections, + ...conditional, + ...core, + ...crypto, + ...date, + ...number, + ...string +}; \ No newline at end of file diff --git a/lib/formatters/number.js b/lib/formatters/number.js new file mode 100644 index 0000000..4ae2d4b --- /dev/null +++ b/lib/formatters/number.js @@ -0,0 +1,73 @@ +const { padNumber, formatNumber } = require('../core/util'); + +module.exports = { + + floor: function(num) { + return Math.floor(Number(num)); + }, + + ceil: function(num) { + return Math.ceil(Number(num)); + }, + + round: function(num) { + return Math.round(Number(num)); + }, + + abs: function(num) { + return Math.abs(Number(num)); + }, + + pow: function(num, exp) { + return Math.pow(num, exp); + }, + + padNumber: function(num, digids) { + num = Number(num); + + if (isNaN(num) || !isFinite(num)) return 'NaN'; + + return padNumber(num, digids); + }, + + formatNumber: function(num, decimals, decimalSeparator, groupingSeparator) { + return formatNumber(num, decimals, decimalSeparator, groupingSeparator); + }, + + hex: function(num) { + return parseInt(String(num), 16) || NaN; + }, + + currency: function(num, unit, decimalSeparator, groupingSeparator, decimals) { + unit = typeof unit == 'string' ? unit : '$'; + decimalSeparator = typeof decimalSeparator == 'string' ? decimalSeparator : '.'; + groupingSeparator = typeof groupingSeparator == 'string' ? groupingSeparator : ','; + decimals = typeof decimals == 'number' ? Math.abs(decimals) : 2; + + let formatted = formatNumber(num, decimals, decimalSeparator, groupingSeparator); + let minus = formatted[0] == '-'; + + return (minus ? '-' : '') + unit + formatted.replace(/^\-/, ''); + }, + + formatSize: function(num, decimals, binary) { + num = Number(num); + + if (isNaN(num) || !isFinite(num)) return 'NaN'; + + decimals = typeof decimals == 'number' ? Math.abs(decimals) : 2; + + let base = binary ? 1024 : 1000; + let suffix = binary ? ['KiB', 'MiB', 'GiB', 'TiB'] : ['kB', 'MB', 'GB', 'TB']; + + for (let i = 3; i >= 0; i--) { + let n = Math.pow(base, i + 1); + if (num >= n) { + return formatNumber(num / n, decimals) + suffix[i]; + } + } + + return num + 'B'; + }, + +}; \ No newline at end of file diff --git a/lib/formatters/string.js b/lib/formatters/string.js new file mode 100644 index 0000000..18499ea --- /dev/null +++ b/lib/formatters/string.js @@ -0,0 +1,210 @@ +const { escapeRegExp } = require('../core/util'); + +module.exports = { + + lowercase: function(str) { + if (str == null) return str; + + return String(str).toLowerCase(); + }, + + uppercase: function(str) { + if (str == null) return str; + + return String(str).toUpperCase(); + }, + + camelize: function(str) { + if (str == null) return str; + + return String(str) + .trim() + .replace(/(\-|_|\s)+(.)?/g, (a, b, c) => c ? c.toUpperCase() : ''); + }, + + capitalize: function(str) { + if (str == null) return str; + + str = String(str); + + return str.charAt(0).toUpperCase() + str.substr(1); + }, + + dasherize: function(str) { + if (str == null) return str; + + return String(str) + .replace(/[_\s]+/g, '-') + .replace(/([A-Z])/g, '-$1') + .replace(/-+/g, '-') + .toLowerCase(); + }, + + humanize: function(str) { + if (str == null) return str; + + str = String(str) + .trim() + .replace(/([a-z\D])([A-Z]+)/g, '$1_$2') + .replace(/[-\s]+/g, '_') + .toLowerCase() + .replace(/_id$/, '') + .replace(/_/g, ' ') + .trim(); + + return str.charAt(0).toUpperCase() + str.substr(1); + }, + + slugify: function(str) { + if (str == null) return str; + + const map = {"2d":"-","20":"-","24":"s","26":"and","30":"0","31":"1","32":"2","33":"3","34":"4","35":"5","36":"6","37":"7","38":"8","39":"9","41":"A","42":"B","43":"C","44":"D","45":"E","46":"F","47":"G","48":"H","49":"I","50":"P","51":"Q","52":"R","53":"S","54":"T","55":"U","56":"V","57":"W","58":"X","59":"Y","61":"a","62":"b","63":"c","64":"d","65":"e","66":"f","67":"g","68":"h","69":"i","70":"p","71":"q","72":"r","73":"s","74":"t","75":"u","76":"v","77":"w","78":"x","79":"y","100":"A","101":"a","102":"A","103":"a","104":"A","105":"a","106":"C","107":"c","108":"C","109":"c","110":"D","111":"d","112":"E","113":"e","114":"E","115":"e","116":"E","117":"e","118":"E","119":"e","120":"G","121":"g","122":"G","123":"g","124":"H","125":"h","126":"H","127":"h","128":"I","129":"i","130":"I","131":"i","132":"IJ","133":"ij","134":"J","135":"j","136":"K","137":"k","138":"k","139":"L","140":"l","141":"L","142":"l","143":"N","144":"n","145":"N","146":"n","147":"N","148":"n","149":"n","150":"O","151":"o","152":"OE","153":"oe","154":"R","155":"r","156":"R","157":"r","158":"R","159":"r","160":"S","161":"s","162":"T","163":"t","164":"T","165":"t","166":"T","167":"t","168":"U","169":"u","170":"U","171":"u","172":"U","173":"u","174":"W","175":"w","176":"Y","177":"y","178":"Y","179":"Z","180":"b","181":"B","182":"b","183":"b","184":"b","185":"b","186":"C","187":"C","188":"c","189":"D","190":"E","191":"F","192":"f","193":"G","194":"Y","195":"h","196":"i","197":"I","198":"K","199":"k","200":"A","201":"a","202":"A","203":"a","204":"E","205":"e","206":"E","207":"e","208":"I","209":"i","210":"R","211":"r","212":"R","213":"r","214":"U","215":"u","216":"U","217":"u","218":"S","219":"s","220":"n","221":"d","222":"8","223":"8","224":"Z","225":"z","226":"A","227":"a","228":"E","229":"e","230":"O","231":"o","232":"Y","233":"y","234":"l","235":"n","236":"t","237":"j","238":"db","239":"qp","240":"<","241":"?","242":"?","243":"B","244":"U","245":"A","246":"E","247":"e","248":"J","249":"j","250":"a","251":"a","252":"a","253":"b","254":"c","255":"e","256":"d","257":"d","258":"e","259":"e","260":"g","261":"g","262":"g","263":"Y","264":"x","265":"u","266":"h","267":"h","268":"i","269":"i","270":"w","271":"m","272":"n","273":"n","274":"N","275":"o","276":"oe","277":"m","278":"o","279":"r","280":"R","281":"R","282":"S","283":"f","284":"f","285":"f","286":"f","287":"t","288":"t","289":"u","290":"Z","291":"Z","292":"3","293":"3","294":"?","295":"?","296":"5","297":"C","298":"O","299":"B","363":"a","364":"e","365":"i","366":"o","367":"u","368":"c","369":"d","386":"A","388":"E","389":"H","390":"i","391":"A","392":"B","393":"r","394":"A","395":"E","396":"Z","397":"H","398":"O","399":"I","400":"E","401":"E","402":"T","403":"r","404":"E","405":"S","406":"I","407":"I","408":"J","409":"jb","410":"A","411":"B","412":"V","413":"G","414":"D","415":"E","416":"ZH","417":"Z","418":"I","419":"Y","420":"R","421":"S","422":"T","423":"U","424":"F","425":"H","426":"TS","427":"CH","428":"SH","429":"SCH","430":"a","431":"b","432":"v","433":"g","434":"d","435":"e","436":"zh","437":"z","438":"i","439":"y","440":"r","441":"s","442":"t","443":"u","444":"f","445":"h","446":"ts","447":"ch","448":"sh","449":"sch","450":"e","451":"e","452":"h","453":"r","454":"e","455":"s","456":"i","457":"i","458":"j","459":"jb","460":"W","461":"w","462":"Tb","463":"tb","464":"IC","465":"ic","466":"A","467":"a","468":"IA","469":"ia","470":"Y","471":"y","472":"O","473":"o","474":"V","475":"v","476":"V","477":"v","478":"Oy","479":"oy","480":"C","481":"c","490":"R","491":"r","492":"F","493":"f","494":"H","495":"h","496":"X","497":"x","498":"3","499":"3","500":"d","501":"d","502":"d","503":"d","504":"R","505":"R","506":"R","507":"R","508":"JT","509":"JT","510":"E","511":"e","512":"JT","513":"jt","514":"JX","515":"JX","531":"U","532":"D","533":"Q","534":"N","535":"T","536":"2","537":"F","538":"r","539":"p","540":"z","541":"2","542":"n","543":"x","544":"U","545":"B","546":"j","547":"t","548":"n","549":"C","550":"R","551":"8","552":"R","553":"O","554":"P","555":"O","556":"S","561":"w","562":"f","563":"q","564":"n","565":"t","566":"q","567":"t","568":"n","569":"p","570":"h","571":"a","572":"n","573":"a","574":"u","575":"j","576":"u","577":"2","578":"n","579":"2","580":"n","581":"g","582":"l","583":"uh","584":"p","585":"o","586":"S","587":"u","4a":"J","4b":"K","4c":"L","4d":"M","4e":"N","4f":"O","5a":"Z","6a":"j","6b":"k","6c":"l","6d":"m","6e":"n","6f":"o","7a":"z","a2":"c","a3":"f","a5":"Y","a7":"s","a9":"c","aa":"a","ae":"r","b2":"2","b3":"3","b5":"u","b6":"p","b9":"1","c0":"A","c1":"A","c2":"A","c3":"A","c4":"A","c5":"A","c6":"AE","c7":"C","c8":"E","c9":"E","ca":"E","cb":"E","cc":"I","cd":"I","ce":"I","cf":"I","d0":"D","d1":"N","d2":"O","d3":"O","d4":"O","d5":"O","d6":"O","d7":"X","d8":"O","d9":"U","da":"U","db":"U","dc":"U","dd":"Y","de":"p","df":"b","e0":"a","e1":"a","e2":"a","e3":"a","e4":"a","e5":"a","e6":"ae","e7":"c","e8":"e","e9":"e","ea":"e","eb":"e","ec":"i","ed":"i","ee":"i","ef":"i","f0":"o","f1":"n","f2":"o","f3":"o","f4":"o","f5":"o","f6":"o","f8":"o","f9":"u","fa":"u","fb":"u","fc":"u","fd":"y","ff":"y","10a":"C","10b":"c","10c":"C","10d":"c","10e":"D","10f":"d","11a":"E","11b":"e","11c":"G","11d":"g","11e":"G","11f":"g","12a":"I","12b":"i","12c":"I","12d":"i","12e":"I","12f":"i","13a":"l","13b":"L","13c":"l","13d":"L","13e":"l","13f":"L","14a":"n","14b":"n","14c":"O","14d":"o","14e":"O","14f":"o","15a":"S","15b":"s","15c":"S","15d":"s","15e":"S","15f":"s","16a":"U","16b":"u","16c":"U","16d":"u","16e":"U","16f":"u","17a":"z","17b":"Z","17c":"z","17d":"Z","17e":"z","17f":"f","18a":"D","18b":"d","18c":"d","18d":"q","18e":"E","18f":"e","19a":"l","19b":"h","19c":"w","19d":"N","19e":"n","19f":"O","1a0":"O","1a1":"o","1a2":"P","1a3":"P","1a4":"P","1a5":"p","1a6":"R","1a7":"S","1a8":"s","1a9":"E","1aa":"l","1ab":"t","1ac":"T","1ad":"t","1ae":"T","1af":"U","1b0":"u","1b1":"U","1b2":"U","1b3":"Y","1b4":"y","1b5":"Z","1b6":"z","1b7":"3","1b8":"3","1b9":"3","1ba":"3","1bb":"2","1bc":"5","1bd":"5","1be":"5","1bf":"p","1c4":"DZ","1c5":"Dz","1c6":"dz","1c7":"Lj","1c8":"Lj","1c9":"lj","1ca":"NJ","1cb":"Nj","1cc":"nj","1cd":"A","1ce":"a","1cf":"I","1d0":"i","1d1":"O","1d2":"o","1d3":"U","1d4":"u","1d5":"U","1d6":"u","1d7":"U","1d8":"u","1d9":"U","1da":"u","1db":"U","1dc":"u","1dd":"e","1de":"A","1df":"a","1e0":"A","1e1":"a","1e2":"AE","1e3":"ae","1e4":"G","1e5":"g","1e6":"G","1e7":"g","1e8":"K","1e9":"k","1ea":"Q","1eb":"q","1ec":"Q","1ed":"q","1ee":"3","1ef":"3","1f0":"J","1f1":"dz","1f2":"dZ","1f3":"DZ","1f4":"g","1f5":"G","1f6":"h","1f7":"p","1f8":"N","1f9":"n","1fa":"A","1fb":"a","1fc":"AE","1fd":"ae","1fe":"O","1ff":"o","20a":"I","20b":"i","20c":"O","20d":"o","20e":"O","20f":"o","21a":"T","21b":"t","21c":"3","21d":"3","21e":"H","21f":"h","22a":"O","22b":"o","22c":"O","22d":"o","22e":"O","22f":"o","23a":"A","23b":"C","23c":"c","23d":"L","23e":"T","23f":"s","24a":"Q","24b":"q","24c":"R","24d":"r","24e":"Y","24f":"y","25a":"e","25b":"3","25c":"3","25d":"3","25e":"3","25f":"j","26a":"i","26b":"I","26c":"I","26d":"I","26e":"h","26f":"w","27a":"R","27b":"r","27c":"R","27d":"R","27e":"r","27f":"r","28a":"u","28b":"v","28c":"A","28d":"M","28e":"Y","28f":"Y","29a":"B","29b":"G","29c":"H","29d":"j","29e":"K","29f":"L","2a0":"q","2a1":"?","2a2":"c","2a3":"dz","2a4":"d3","2a5":"dz","2a6":"ts","2a7":"tf","2a8":"tc","2a9":"fn","2aa":"ls","2ab":"lz","2ac":"ww","2ae":"u","2af":"u","2b0":"h","2b1":"h","2b2":"j","2b3":"r","2b4":"r","2b5":"r","2b6":"R","2b7":"W","2b8":"Y","2df":"x","2e0":"Y","2e1":"1","2e2":"s","2e3":"x","2e4":"c","36a":"h","36b":"m","36c":"r","36d":"t","36e":"v","36f":"x","37b":"c","37c":"c","37d":"c","38a":"I","38c":"O","38e":"Y","38f":"O","39a":"K","39b":"A","39c":"M","39d":"N","39e":"E","39f":"O","3a0":"TT","3a1":"P","3a3":"E","3a4":"T","3a5":"Y","3a6":"O","3a7":"X","3a8":"Y","3a9":"O","3aa":"I","3ab":"Y","3ac":"a","3ad":"e","3ae":"n","3af":"i","3b0":"v","3b1":"a","3b2":"b","3b3":"y","3b4":"d","3b5":"e","3b6":"c","3b7":"n","3b8":"0","3b9":"1","3ba":"k","3bb":"j","3bc":"u","3bd":"v","3be":"c","3bf":"o","3c0":"tt","3c1":"p","3c2":"s","3c3":"o","3c4":"t","3c5":"u","3c6":"q","3c7":"X","3c8":"Y","3c9":"w","3ca":"i","3cb":"u","3cc":"o","3cd":"u","3ce":"w","3d0":"b","3d1":"e","3d2":"Y","3d3":"Y","3d4":"Y","3d5":"O","3d6":"w","3d7":"x","3d8":"Q","3d9":"q","3da":"C","3db":"c","3dc":"F","3dd":"f","3de":"N","3df":"N","3e2":"W","3e3":"w","3e4":"q","3e5":"q","3e6":"h","3e7":"e","3e8":"S","3e9":"s","3ea":"X","3eb":"x","3ec":"6","3ed":"6","3ee":"t","3ef":"t","3f0":"x","3f1":"e","3f2":"c","3f3":"j","3f4":"O","3f5":"E","3f6":"E","3f7":"p","3f8":"p","3f9":"C","3fa":"M","3fb":"M","3fc":"p","3fd":"C","3fe":"C","3ff":"C","40a":"Hb","40b":"Th","40c":"K","40d":"N","40e":"Y","40f":"U","41a":"K","41b":"L","41c":"M","41d":"N","41e":"O","41f":"P","42a":"","42b":"Y","42c":"","42d":"E","42e":"U","42f":"YA","43a":"k","43b":"l","43c":"m","43d":"n","43e":"o","43f":"p","44a":"","44b":"y","44c":"","44d":"e","44e":"u","44f":"ya","45a":"Hb","45b":"h","45c":"k","45d":"n","45e":"y","45f":"u","46a":"mY","46b":"my","46c":"Im","46d":"Im","46e":"3","46f":"3","47a":"O","47b":"o","47c":"W","47d":"w","47e":"W","47f":"W","48a":"H","48b":"H","48c":"B","48d":"b","48e":"P","48f":"p","49a":"K","49b":"k","49c":"K","49d":"k","49e":"K","49f":"k","4a0":"K","4a1":"k","4a2":"H","4a3":"h","4a4":"H","4a5":"h","4a6":"Ih","4a7":"ih","4a8":"O","4a9":"o","4aa":"C","4ab":"c","4ac":"T","4ad":"t","4ae":"Y","4af":"y","4b0":"Y","4b1":"y","4b2":"X","4b3":"x","4b4":"TI","4b5":"ti","4b6":"H","4b7":"h","4b8":"H","4b9":"h","4ba":"H","4bb":"h","4bc":"E","4bd":"e","4be":"E","4bf":"e","4c0":"I","4c1":"X","4c2":"x","4c3":"K","4c4":"k","4c5":"jt","4c6":"jt","4c7":"H","4c8":"h","4c9":"H","4ca":"h","4cb":"H","4cc":"h","4cd":"M","4ce":"m","4cf":"l","4d0":"A","4d1":"a","4d2":"A","4d3":"a","4d4":"AE","4d5":"ae","4d6":"E","4d7":"e","4d8":"e","4d9":"e","4da":"E","4db":"e","4dc":"X","4dd":"X","4de":"3","4df":"3","4e0":"3","4e1":"3","4e2":"N","4e3":"n","4e4":"N","4e5":"n","4e6":"O","4e7":"o","4e8":"O","4e9":"o","4ea":"O","4eb":"o","4ec":"E","4ed":"e","4ee":"Y","4ef":"y","4f0":"Y","4f1":"y","4f2":"Y","4f3":"y","4f4":"H","4f5":"h","4f6":"R","4f7":"r","4f8":"bI","4f9":"bi","4fa":"F","4fb":"f","4fc":"X","4fd":"x","4fe":"X","4ff":"x","50a":"H","50b":"h","50c":"G","50d":"g","50e":"T","50f":"t","51a":"Q","51b":"q","51c":"W","51d":"w","53a":"d","53b":"r","53c":"L","53d":"Iu","53e":"O","53f":"y","54a":"m","54b":"o","54c":"N","54d":"U","54e":"Y","54f":"S","56a":"d","56b":"h","56c":"l","56d":"lu","56e":"d","56f":"y","57a":"w","57b":"2","57c":"n","57d":"u","57e":"y","57f":"un"}; + + let clean = ''; + for (let i = 0; i < str.length; i++) { + clean += map[str.charCodeAt(i).toString(16)] || ''; + } + + return clean.toLowerCase().replace(/-+/g, '-').replace(/^-|-$/, ''); + }, + + underscore: function(str) { + if (str == null) return str; + + return String(str) + .trim() + .replace(/([a-z\d])([A-Z]+)/g, '$1_$2') + .replace(/[-\s]+/g, '_') + .toLowerCase(); + }, + + titlecase: function(str) { + if (str == null) return str; + + return String(str) + .replace(/(?:^|\s)\S/g, a => a.toUpperCase()); + }, + + camelcase: function(str) { + if (str == null) return str; + + return String(str) + .toLowerCase() + .replace(/\s+(\S)/g, (a, b) => b.toUpperCase()); + }, + + replace: function(str, search, value) { + if (str == null) return str; + + if (typeof search == 'string') { + search = new RegExp(escapeRegExp(search), 'g'); + } + + return String(str).replace(search, value); + }, + + trim: function(str) { + if (str == null) return str; + + return String(str).trim(); + }, + + split: function(str, separator) { + if (str == null) return []; + + return String(str).split(separator); + }, + + pad: function(str, length, chr, pos) { + if (str == null) return str; + + str = String(str); + chr = chr && typeof chr == 'string' ? chr[0] : ' '; + + if (str.length < length) { + let n = length - str.length; + + switch (pos) { + case 'right': + str += chr.repeat(n); + break; + + case 'center': + str = chr.repeat(Math.floor(n / 2)) + str + chr.repeat(Math.ceil(n / 2)); + break; + + default: + str = chr.repeat(n) + str; + } + } + + return str; + }, + + repeat: function(str, num) { + if (str == null) return str; + + return String(str).repeat(num); + }, + + substr: function(str, start, length) { + if (str == null) return str; + + return String(str).substr(start, length); + }, + + trunc: function(str, length, useWordBoundary, fragment) { + if (str == null) return str; + + str = String(str); + length = Number(length); + fragment = fragment && typeof fragment == 'string' ? fragment : '\u2026'; + + if (str.length > length) { + str = str.substr(0, length - fragment.length); + + if (useWordBoundary && str.includes(' ')) { + str = str.substr(0, str.lastIndexOf(' ')); + } + + str += fragment; + } + + return str; + }, + + stripTags: function(str) { + if (str == null) return str; + + return String(str).replace(/<[^>]+>/g, ''); + }, + + wordCount: function(str) { + if (str == null) return 0; + + return String(str) + .replace(/\.\?!,/g, ' ') + .trim() + .split(/\s+/) + .length; + }, + + length: function(str) { + if (str == null) return 0; + + return String(str).length; + }, + + urlencode: function(str) { + if (str == null) return str; + + return encodeURI(String(str)); + }, + + urldecode: function(str) { + if (str == null) return str; + + return decodeURI(String(str)); + }, + +}; \ No newline at end of file diff --git a/lib/locale/en-US.js b/lib/locale/en-US.js new file mode 100644 index 0000000..792513c --- /dev/null +++ b/lib/locale/en-US.js @@ -0,0 +1,59 @@ +module.exports = { + months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + monthsShort: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + daysShort: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + + validator: { + core: { + required: 'This field is required.', + email: 'Please enter a valid email address.', + url: 'Please enter a valid URL.', + date: 'Please enter a valid date.', + time: 'Please enter a valid time.', + month: 'Please enter a valid month.', + week: 'Please enter a valid week.', + color: 'Please enter a color in the format #xxxxxx.', + pattern: 'Invalid format.', + number: 'Please enter a valid number.', + digits: 'Please enter only digits.', + alphanumeric: 'Letters, numbers and underscores only please.', + creditcard: 'Please enter a valid credit card number.', + bic: 'Please specify a valid BIC code.', + iban: 'Please specify a valid IBAN.', + vat: 'Please specify a valid VAT number.', + integer: 'A positive or negative non-decimal number please.', + ipv4: 'Please enter a valid IP v4 address.', + ipv6: 'Please enter a valid IP v6 address.', + lettersonly: 'Letters only please.', + unicodelettersonly: 'Letters only please.', + letterswithbasicpunc: 'Letters or punctuation only please.', + nowhitespace: 'No whitespace please.', + minlength: 'Please enter at least {0} characters.', + maxlength: 'Please enter no more than {0} characters.', + minitems: 'Please select at least {0} items.', + maxitems: 'Please select no more than {0} items.', + min: 'Please enter a value greater than or equal to {0}.', + max: 'Please enter a value less than or equal to {0}.', + equalTo: 'Please enter the same value again.', + notEqualTo: 'Please enter a different value, values must not be the same.', + }, + db: { + exists: 'Value does not exist in database.', + notexists: 'Value already exists in database.', + }, + unicode: { + unicodelettersonly: 'Entered characters are not allowed.', + unicodescripts: 'Entered characters are not allowed.', + }, + upload: { + accept: 'Please select a file with a valid file type.', + minsize: 'Please select a file of at least {0} bytes.', + maxsize: 'Please select a file of no more than {0} bytes.', + mintotalsize: 'Total size of selected files should be at least {0} bytes.', + maxtotalsize: 'Total size of selected files should be no more than {0} bytes.', + minfiles: 'Please select at least {0} files.', + maxfiles: 'Please select no more than {0} files.', + }, + }, +}; \ No newline at end of file diff --git a/lib/modules/api.js b/lib/modules/api.js new file mode 100644 index 0000000..98da843 --- /dev/null +++ b/lib/modules/api.js @@ -0,0 +1,117 @@ +const { http, https } = require('follow-redirects'); +const querystring = require('querystring'); +const zlib = require('zlib'); +const pkg = require('../../package.json'); + +module.exports = { + + send: async function(options) { + let url = this.parseRequired(options.url, 'string', 'api.send: url is required.'); + let method = this.parseOptional(options.method, 'string', 'GET'); + let data = this.parseOptional(options.data, '*', ''); + let dataType = this.parseOptional(options.dataType, 'string', 'auto'); + let verifySSL = this.parseOptional(options.verifySSL, 'boolean', false); + let params = this.parseOptional(options.params, 'object', null); + let headers = this.parseOptional(options.headers, 'object', {}); + let username = this.parseOptional(options.username, 'string', ''); + let password = this.parseOptional(options.password, 'string', ''); + let oauth = this.parseOptional(options.oauth, 'string', ''); + let passErrors = this.parseOptional(options.passErrors, 'boolean', true); + let timeout = this.parseOptional(options.timeout, 'number', 0); + + if (params) { + url += '?' + querystring.stringify(params); + } + + if (dataType == 'auto' && method == 'POST') { + dataType = 'x-www-form-urlencoded'; + } + + if (dataType != 'auto' && !headers['Content-Type']) { + headers['Content-Type'] = `application/${dataType}`; + } + + if (dataType == 'x-www-form-urlencoded') { + data = querystring.stringify(data); + } else if (typeof data != 'string') { + data = JSON.stringify(data); + } + + if (data) { + headers['Content-Length'] = Buffer.byteLength(data); + } + + const Url = new URL(url); + const opts = { method, headers, rejectUnauthorized: !!verifySSL, maxBodyLength: 1000000000 }; + + if (timeout > 0) { + opts.timeout = timeout; + } + + if (username || password) { + opts.auth = `${username}:${password}`; + } + + if (oauth) { + //const provider = this.oauth[oauth]; + const provider = await this.getOAuthProvider(oauth); + if (provider && provider.access_token) { + headers['Authorization'] = 'Bearer ' + provider.access_token; + } + } + + if (!headers['User-Agent']) headers['User-Agent'] = `${pkg.name}/${pkg.version}`; + if (!headers['Accept']) headers['Accept'] = 'application/json'; + + return new Promise((resolve, reject) => { + const req = (Url.protocol == 'https:' ? https : http).request(Url, opts, res => { + let body = ''; + + let output = res; + if (res.headers['content-encoding'] == 'br') { + output = res.pipe(zlib.createBrotliDecompress()); + } + if (res.headers['content-encoding'] == 'gzip') { + output = res.pipe(zlib.createGunzip()); + } + if (res.headers['content-encoding'] == 'deflate') { + output = res.pipe(zlib.createInflate()); + } + + output.setEncoding('utf8'); + output.on('data', chunk => body += chunk); + output.on('end', () => { + if (passErrors && res.statusCode >= 400) { + if (this.res.status) { + this.res.status(res.statusCode).send(body); + } + return reject(body); + } + + if (body.charCodeAt(0) === 0xFEFF) { + body = body.slice(1); + } + + if (res.headers['content-type'] && res.headers['content-type'].includes('json')) { + try { + body = JSON.parse(body); + } catch(e) { + console.error(e); + } + } + + resolve({ + status: res.statusCode, + headers: res.headers, + data: body + }); + }); + }); + + req.on('error', reject); + req.write(data); + req.end(); + }); + }, + +}; \ No newline at end of file diff --git a/lib/modules/arraylist.js b/lib/modules/arraylist.js new file mode 100644 index 0000000..79e2fa5 --- /dev/null +++ b/lib/modules/arraylist.js @@ -0,0 +1,173 @@ +// ArrayList + +const { clone } = require('../core/util'); + +// Create a new ArrayList +exports.create = function(options, name) { + const value = this.parseOptional(options.value, 'object', []); + + if (!name) throw Error('arraylist.create: name is required.'); + + if (!this.req.arrays) { + this.req.arrays = {}; + } + + this.req.arrays[name] = Array.isArray(value) ? clone(value) : []; +}; + +// Return the ArrayList as array value +exports.value = function(options) { + const ref = this.parseRequired(options.ref, 'string', 'arraylist.value: ref is required.'); + + if (!this.req.arrays) throw Error('arraylist.value: No ArrayList are created.'); + if (!this.req.arrays[ref]) throw Error(`arraylist.value: ArrayList ${ref} not found.`); + + return clone(this.req.arrays[ref]); +}; + +// Return the size of the ArrayList +exports.size = function(options) { + const ref = this.parseRequired(options.ref, 'string', 'arraylist.size: ref is required.'); + + if (!this.req.arrays) throw Error('arraylist.size: No arraylists are created.'); + if (!this.req.arrays[ref]) throw Error(`arraylist.size: ArrayList ${ref} not found.`); + + return this.req.arrays[ref].length; +}; + +// Return the value at a specific index +exports.get = function(options) { + const ref = this.parseRequired(options.ref, 'string', 'arraylist.get: ref is required.'); + const index = this.parseRequired(options.index, 'number', 'arraylist.get: index is required.'); + + if (!this.req.arrays) throw Error('arraylist.get: No ArrayList are created.'); + if (!this.req.arrays[ref]) throw Error(`arraylist.get: ArrayList ${ref} not found.`); + + return clone(this.req.arrays[ref][index]); +}; + +// Add a value to the ArrayList +exports.add = function(options) { + const ref = this.parseRequired(options.ref, 'string', 'arraylist.add: ref is required.'); + const value = this.parseRequired(options.value, '*', 'arraylist.add: value is required.'); + + if (!this.req.arrays) throw Error('arraylist.add: No ArrayList are created.'); + if (!this.req.arrays[ref]) throw Error(`arraylist.add: ArrayList ${ref} not found.`); + + this.req.arrays[ref].push(clone(value)); +}; + +// Add an array of values to the ArrayList +exports.addAll = function(options) { + const ref = this.parseRequired(options.ref, 'string', 'arraylist.addAll: ref is required.'); + const value = this.parseRequired(options.value, 'object', 'arraylist.addAll: value is required.'); + + if (!this.req.arrays) throw Error('arraylist.addAll: No ArrayList are created.'); + if (!this.req.arrays[ref]) throw Error(`arraylist.addAll: ArrayList ${ref} not found.`); + if (!Array.isArray(value)) throw Error('arraylist.addAll: Value must be an array.') + + for (let val of value) { + this.req.arrays[ref].push(clone(val)); + } +}; + +// Update a value at a specific index +exports.set = function(options) { + const ref = this.parseRequired(options.ref, 'string', 'arraylist.set: ref is required.'); + const index = this.parseRequired(options.index, 'number', 'arraylist.set: index is required.'); + const value = this.parseRequired(options.value, '*', 'arraylist.set: value is required.'); + + if (!this.req.arrays) throw Error('arraylist.set: No ArrayList are created.'); + if (!this.req.arrays[ref]) throw Error(`arraylist.set: ArrayList ${ref} not found.`); + + this.req.arrays[ref][index] = clone(value); +}; + +// Remove the first occurrence of a specific value +exports.remove = function(options) { + const ref = this.parseRequired(options.ref, 'string', 'arraylist.remove: ref is required.'); + const value = this.parseRequired(options.value, '*', 'arraylist.remove: value is required.'); + + if (!this.req.arrays) throw Error('arraylist.remove: No ArrayList are created.'); + if (!this.req.arrays[ref]) throw Error(`arraylist.remove: ArrayList ${ref} not found.`); + + const index = this.req.arrays[ref].indexOf(value); + + if (index == -1) throw Error('arraylist.remove: Value does not exist in the ArrayList.'); + + this.req.arrays[ref].splice(index, 1); +}; + +// Remove a value from the ArrayList at a specific index +exports.removeAt = function(options) { + const ref = this.parseRequired(options.ref, 'string', 'arraylist.removeAt: ref is required.'); + const index = this.parseRequired(options.index, 'number', 'arraylist.removeAt: index is required.'); + + if (!this.req.arrays) throw Error('arraylist.removeAt: No ArrayList are created.'); + if (!this.req.arrays[ref]) throw Error(`arraylist.removeAt: ArrayList ${ref} not found.`); + + this.req.arrays[ref].splice(index, 1); +}; + +// Clear all values from the ArrayList +exports.clear = function(options) { + const ref = this.parseRequired(options.ref, 'string', 'arraylist.clear: ref is required.'); + + if (!this.req.arrays) throw Error('arraylist.clear: No ArrayList are created.'); + if (!this.req.arrays[ref]) throw Error(`arraylist.clear: ArrayList ${ref} not found.`); + + this.req.arrays[ref] = []; +}; + +// Sort the ArrayList +exports.sort = function(options) { + const ref = this.parseRequired(options.ref, 'string', 'arraylist.sort: ref is required.'); + const prop = this.parseOptional(options.prop, 'string', null); + const desc = this.parseOptional(options.desc, 'boolean', false); + + if (!this.req.arrays) throw Error('arraylist.sort: No ArrayList are created.'); + if (!this.req.arrays[ref]) throw Error(`arraylist.sort: ArrayList ${ref} not found.`); + + this.req.arrays[ref].sort((a, b) => { + if (prop) { + a = a[prop]; + b = b[prop]; + } + + if (a == b) return 0; + if (a < b) return desc ? 1 : -1; + return desc ? -1 : 1; + }); +}; + +// Return the index of the first occurrence of a specific value +exports.indexOf = function(options) { + const ref = this.parseRequired(options.ref, 'string', 'arraylist.indexOf: ref is required.'); + const value = this.parseRequired(options.value, '*', 'arraylist.indexOf: value is required.'); + + if (!this.req.arrays) throw Error('arraylist.indexOf: No ArrayList are created.'); + if (!this.req.arrays[ref]) throw Error(`arraylist.indexOf: ArrayList ${ref} not found.`); + + return this.req.arrays[ref].indexOf(value); +}; + +// Return true if the ArrayList contains a specific value +exports.contains = function(options) { + const ref = this.parseRequired(options.ref, 'string', 'arraylist.contains: ref is required.'); + const value = this.parseRequired(options.value, '*', 'arraylist.contains: value is required.'); + + if (!this.req.arrays) throw Error('arraylist.contains: No ArrayList are created.'); + if (!this.req.arrays[ref]) throw Error(`arraylist.contains: ArrayList ${ref} not found.`); + + return this.req.arrays[ref].includes(value); +}; + +// Return true when ArrayList is empty +exports.isEmpty = function(options) { + const ref = this.parseRequired(options.ref, 'string', 'arraylist.isEmpty: ref is required.'); + + if (!this.req.arrays) throw Error('arraylist.isEmpty: No ArrayList are created.'); + if (!this.req.arrays[ref]) throw Error(`arraylist.isEmpty: ArrayList ${ref} not found.`); + + return !this.req.arrays[ref].length; +}; \ No newline at end of file diff --git a/lib/modules/auth.js b/lib/modules/auth.js new file mode 100644 index 0000000..18bb060 --- /dev/null +++ b/lib/modules/auth.js @@ -0,0 +1,53 @@ +module.exports = { + + provider: async function(options, name) { + const provider = await this.setAuthProvider(name, options); + + return { identity: provider.identity }; + }, + + identify: async function(options) { + const provider = await this.getAuthProvider(this.parseRequired(options.provider, 'string', 'auth.validate: provider is required.')); + + return provider.identity; + }, + + validate: async function(options) { + const provider = await this.getAuthProvider(this.parseRequired(options.provider, 'string', 'auth.validate: provider is required.')); + const { action, username, password, remember } = this.req.body; + + if (action == 'login') { + return provider.login(username, password, remember); + } + + if (action == 'logout') { + return provider.logout(); + } + + if (!provider.identity) { + return provider.unauthorized(); + } + }, + + login: async function(options) { + const provider = await this.getAuthProvider(this.parseRequired(options.provider, 'string', 'auth.login: provider is required.')); + const username = this.parseOptional(options.username, 'string', this.parse('{{$_POST.username}}')); + const password = this.parseOptional(options.password, 'string', this.parse('{{$_POST.password}}')); + const remember = this.parseOptional(options.remember, '*', this.parse('{{$_POST.remember}}')); + + return provider.login(username, password, remember); + }, + + logout: async function(options) { + const provider = await this.getAuthProvider(this.parseRequired(options.provider, 'string', 'auth.logout: provider is required.')); + + return provider.logout(); + }, + + restrict: async function(options) { + const provider = await this.getAuthProvider(this.parseRequired(options.provider, 'string', 'auth.restrict: provider is required.')); + + return provider.restrict(this.parse(options)); + }, + +}; \ No newline at end of file diff --git a/lib/modules/collections.js b/lib/modules/collections.js new file mode 100644 index 0000000..7cec883 --- /dev/null +++ b/lib/modules/collections.js @@ -0,0 +1,185 @@ +const { clone } = require('../core/util'); + +module.exports = { + + addColumns: function(options) { + let collection = this.parseRequired(options.collection, 'object'/*array[object]*/, 'collections.addColumns: collection is required.'); + let add = this.parseRequired(options.add, 'object', 'collections.addColumns: add is required.'); + let overwrite = this.parseOptional(options.overwrite, 'boolean', false); + let output = []; + + for (let row of collection) { + let newRow = clone(row); + + for (let column in add) { + if (overwrite || newRow[column] == null) { + newRow[column] = add[column]; + } + } + + output.push(newRow); + } + + return output; + }, + + filterColumns: function(options) { + let collection = this.parseRequired(options.collection, 'object'/*array[object]*/, 'collections.filterColumns: collection is required.'); + let columns = this.parseRequired(options.columns, 'object'/*array[string]*/, 'collections.filterColumns: columns is required.'); + let keep = this.parseOptional(options.keep, 'boolean', false); + let output = []; + + for (let row of collection) { + let newRow = {}; + + for (let column in row) { + if (columns.includes(column)) { + if (keep) { + newRow[column] = clone(row[column]); + } + } else if (!keep) { + newRow[column] = clone(row[column]); + } + } + + output.push(newRow); + } + + return output; + }, + + renameColumns: function(options) { + let collection = this.parseRequired(options.collection, 'object'/*array[object]*/, 'collections.renamecolumns: collection is required.'); + let rename = this.parseRequired(options.rename, 'object', 'collections.renamecolumns: rename is required.'); + let output = []; + + for (let row of collection) { + let newRow = {}; + + for (let column in row) { + newRow[rename[column] || column] = clone(row[column]); + } + + output.push(newRow); + } + + return output; + }, + + fillDown: function(options) { + let collection = this.parseRequired(options.collection, 'object'/*array[object]*/, 'collections.fillDown: collection is required.'); + let columns = this.parseRequired(options.columns, 'object'/*array[string]*/, 'collections.fillDown: columns is required.'); + let output = []; + let values = {}; + + for (let column of columns) { + values[column] = null; + } + + for (let row of collection) { + let newRow = clone(row); + + for (let column in values) { + if (newRow[column] == null) { + newRow[column] = values[column]; + } else { + values[column] = newRow[column]; + } + } + + output.push(newRow); + } + + return output; + }, + + addRows: function(options) { + let collection = this.parseRequired(options.collection, 'object'/*array[object]*/, 'collections.addRows: collection is required.'); + let rows = this.parseRequired(options.rows, 'object'/*array[object]*/, 'collections.addRows: rows is required.'); + + return clone(collection).concat(clone(rows)); + }, + + addRownumbers: function(options) { + let collection = this.parseRequired(options.collection, 'object'/*array[object]*/, 'collections.addRownumbers: collection is required.'); + let column = this.parseOptional(options.column, 'string', 'nr'); + let startAt = this.parseOptional(options.startAt, 'number', 1); + let desc = this.parseOptional(options.desc, 'boolean', false); + let output = []; + let nr = desc ? collection.length + startAt - 1 : startAt; + + for (let row of collection) { + let newRow = clone(row); + + newRow[column] = desc ? nr-- : nr++; + + output.push(newRow); + } + + return output; + }, + + join: function(options) { + let collection1 = this.parseRequired(options.collection1, 'object'/*array[object]*/, 'collections.join: collection1 is required.'); + let collection2 = this.parseRequired(options.collection2, 'object'/*array[object]*/, 'collections.join: collection2 is required.'); + let matches = this.parseRequired(options.matches, 'object', 'collections.join: matches is required.'); + let matchAll = this.parseOptional(options.matchAll, 'boolean', false); + let output = []; + + for (let row1 of collection1) { + let newRow = clone(row1); + + for (let row2 of collection2) { + let join = false; + + for (let match in matches) { + if (row1[match] == row2[matches[match]]) { + join = true; + if (!matchAll) break; + } else if (matchAll) { + join = false; + break; + } + } + + if (join) { + for (let column in row2) { + newRow[column] = clone(row2[column]); + } + break; + } + } + + output.push(newRow); + } + + return output; + }, + + normalize: function(options) { + let collection = this.parseRequired(options.collection, 'object'/*array[object]*/, 'collections.normalize: collection is required.'); + let columns = []; + let output = []; + + for (let row of collection) { + for (let column in row) { + if (!columns.includes(column)) { + columns.push(column); + } + } + } + + for (let row of collection) { + let newRow = {}; + + for (let column of columns) { + newRow[column] = row[column] == null ? null : clone(row[column]); + } + + output.push(newRow); + } + + return output; + }, + +}; \ No newline at end of file diff --git a/lib/modules/core.js b/lib/modules/core.js new file mode 100644 index 0000000..b564aa9 --- /dev/null +++ b/lib/modules/core.js @@ -0,0 +1,269 @@ +const fs = require('fs-extra'); +const { clone } = require('../core/util'); + +module.exports = { + + wait: function(options) { + let delay = this.parseOptional(options.delay, 'number', 1000); + return new Promise(resolve => setTimeout(resolve, delay)) + }, + + log: function(options) { + let message = this.parse(options.message); + console.log(message); + return message; + }, + + repeat: async function(options) { + let repeater = this.parseRequired(options.repeat, '*', 'core.repeater: repeat is required.'); + let outputFilter = this.parseOptional(options.outputFilter, 'string', 'include'); // include/exclude + let outputFields = this.parseOptional(options.outputFields, 'object'/*array[string]*/, []); + let index = 0, data = [], parentData = this.data; + + switch (typeof repeater) { + case 'boolean': + repeater = repeater ? [0] : []; + break; + + case 'string': + repeater = repeater.split(','); + break; + + case 'number': + repeater = (n => { + let a = [], i = 0; + while (i < n) a.push(i++); + return a; + })(repeater); + break; + } + + if (!(Array.isArray(repeater) || repeater instanceof Object)) { + throw new Error('Repeater data is not an array or object'); + } + + for (let key in repeater) { + if (repeater.hasOwnProperty(key)) { + let scope = {}; + this.data = {}; + + if (repeater[key] instanceof Object) { + for (var prop in repeater[key]) { + if (repeater[key].hasOwnProperty(prop)) { + scope[prop] = repeater[key][prop]; + + if (outputFilter == 'exclude') { + if (!outputFields.includes(prop)) { + this.data[prop] = repeater[key][prop]; + } + } else { + if (outputFields.includes(prop)) { + this.data[prop] = repeater[key][prop]; + } + } + } + } + } + + scope.$key = key; + scope.$name = key; + scope.$value = clone(repeater[key]); + scope.$index = index; + scope.$number = index + 1; + scope.$oddeven = index % 2; + + if (repeater[key] == null) { + repeater[key] = {}; + } + + this.scope = this.scope.create(scope, clone(repeater[key])); + await this.exec(options.exec, true); + this.scope = this.scope.parent; + + data.push({ ...this.data }); + + index++; + } + } + + this.data = parentData; + + return data; + }, + + while: async function(options) { + let max = this.parseOptional(options.max, 'number', Number.MAX_SAFE_INTEGER); + let i = 0; + + while (this.parse(options.while)) { + await this.exec(options.exec, true); + if (++i == max) break; + } + }, + + condition: async function(options) { + let condition = this.parse(options.if); + + if (!!condition) { + if (options.then) { + await this.exec(options.then, true); + } + } else if (options.else) { + await this.exec(options.else, true); + } + }, + + conditions: async function(options) { + if (Array.isArray(options.conditions)) { + for (let condition of options.conditions) { + let when = this.parse(condition.when); + + if (!!when) { + return this.exec(condition.then, true); + } + } + } + }, + + select: async function(options) { + let expression = this.parse(options.expression); + + if (Array.isArray(options.cases)) { + for (let item of options.cases) { + let value = this.parse(item.value); + + if (expression === value) { + return this.exec(item.exec, true); + } + } + } + }, + + setvalue: function(options) { + let key = this.parseOptional(options.key, 'string', ''); + let value = this.parse(options.value); + if (key) this.set(key, value); + return value; + }, + + setsession: function(options, name) { + let value = this.parse(options.value); + this.req.session[name] = value; + return value; + }, + + removesession: function(options, name) { + delete this.req.session[name]; + }, + + setcookie: function(options, name) { + options = this.parse(options); + + let cookieOptions = { + domain: options.domain || undefined, + httpOnly: !!options.httpOnly, + maxAge: options.expires === 0 ? undefined : (options.expires || 30) * 24 * 60 * 60 * 1000, // from days to ms + path: options.path || '/', + secure: !!options.secure, + sameSite: options.sameSite || false + }; + + this.setCookie(name, options.value, cookieOptions); + }, + + removecookie: function(options, name) { + options = this.parse(options); + + let cookieOptions = { + domain: options.domain || undefined, + httpOnly: !!options.httpOnly, + maxAge: options.expires === 0 ? undefined : (options.expires || 30) * 24 * 60 * 60 * 1000, // from days to ms + path: options.path || '/', + secure: !!options.secure, + sameSite: !!options.sameSite + }; + + this.removeCookie(name, cookieOptions); + }, + + response: function(options) { + let data = this.parseOptional(options.data, '*', null); + let status = this.parseOptional(options.status, 'number', 200); + let contentType = this.parseOptional(options.contentType, 'string', 'application/json'); + if (contentType != 'application/json') { + this.res.set('Content-Type', contentType); + this.res.status(status).send(data); + } else { + this.res.status(status).json(data); + } + }, + + error: function(options) { + let message = this.parseRequired(options.message, 'string', 'core.error: message is required.'); + throw new Error(message); + }, + + redirect: function(options) { + let url = this.parseRequired(options.url, 'string', 'core.redirect: url is required.'); + let status = this.parseOptional(options.status, 'number', 302); + + this.res.redirect(status == 301 ? 301 : 302, url); + }, + + trycatch: async function(options) { + try { + await this.exec(options.try, true); + } catch (error) { + this.scope.set('$_ERROR', error.message || error); + this.error = false; + if (options.catch) { + await this.exec(options.catch, true); + } + } + }, + + exec: async function(options) { + var data = {}; + + if (options.exec && fs.existsSync(`app/modules/lib/${options.exec}.json`)) { + let parentData = this.data; + this.data = {}; + this.scope = this.scope.create({ $_PARAM: this.parse(options.params) }); + await this.exec(await fs.readJSON(`app/modules/lib/${options.exec}.json`), true); + data = this.data; + this.scope = this.scope.parent; + this.data = parentData; + } else { + throw new Error(`There is no action called '${options.exec}' found in the library.`); + } + + return data; + }, + + group: async function(options, name) { + if (name) { + return this.sub(options.exec); + } else { + return this.exec(options.exec, true); + } + }, + + parallel: async function(options, name) { + let actions = options.exec.steps || options.exec; + if (!Array.isArray(actions)) actions = [actions]; + return await Promise.all(actions.map(exec => { + if (name) { + return this.sub(exec); + } else { + return this.exec(exec, true); + } + })); + }, + + randomUUID: function(options) { + const { randomUUID } = require('crypto'); + const { v4: uuidv4 } = require('uuid'); + return randomUUID ? randomUUID() : uuidv4(); + }, + +}; \ No newline at end of file diff --git a/lib/modules/crypto.js b/lib/modules/crypto.js new file mode 100644 index 0000000..11e274e --- /dev/null +++ b/lib/modules/crypto.js @@ -0,0 +1,31 @@ +// Perhaps use hashy as reference to implement bcrypt and needRehash action +// https://github.com/JsCommunity/hashy + +// supports argon2i, argon2d and argon2id +exports.passwordHash = async function(options) { + const password = this.parseRequired(options.password, 'string', 'crypto.passwordHash: password is required.') + const algo = this.parseOptional(options.algo, 'string', 'argon2i') + const argon2 = require('argon2') + const type = argon2[algo] + return argon2.hash(password, { type }) +} + +exports.passwordVerify = async function(options) { + const password = this.parseRequired(options.password, 'string', 'crypto.passwordVerify: password is required.') + const hash = this.parseRequired(options.hash, 'string', 'crypto.passwordVerify: hash is required.') + const argon2 = require('argon2') + return argon2.verify(hash, password) +} + +exports.passwordNeedsRehash = async function(options) { + const hash = this.parseRequired(options.hash, 'string', 'crypto.passwordNeedsRehash: hash is required.') + const algo = this.parseOptional(options.algo, 'string', 'argon2i') + const argon2 = require('argon2') + return argon2.needsRehash(hash, {}) +} + +exports.uuid = async function(options) { + const { randomUUID } = require('crypto') + const { v4: uuidv4 } = require('uuid') + return randomUUID ? randomUUID() : uuidv4() +} \ No newline at end of file diff --git a/lib/modules/dataset.js b/lib/modules/dataset.js new file mode 100644 index 0000000..b28dca3 --- /dev/null +++ b/lib/modules/dataset.js @@ -0,0 +1,24 @@ +const { readFileSync } = require('fs'); +const { resolve } = require('path'); + +module.exports = { + + json: function(options) { + return JSON.parse(this.parse(options.data)); + }, + + local: function(options) { + let path = this.parse(options.path); + + if (typeof path !== 'string') throw new Error('dataset.local: path is required.'); + + let data = readFileSync(resolve('public', path)); + + return JSON.parse(data); + }, + + remote: function(options) { + throw new Error('dataset.remote: not implemented, use api instead.'); + }, + +}; \ No newline at end of file diff --git a/lib/modules/dbconnector.js b/lib/modules/dbconnector.js new file mode 100644 index 0000000..913c51e --- /dev/null +++ b/lib/modules/dbconnector.js @@ -0,0 +1,765 @@ +const db = require('../core/db'); +const { where } = require('../formatters'); +const debug = require('debug')('server-connect:db'); + +module.exports = { + + connect: function(options, name) { + if (!name) throw new Error('dbconnector.connect has no name.'); + this.setDbConnection(name, options); + }, + + select: async function(options, name, meta) { + const connection = this.parseRequired(options.connection, 'string', 'dbconnector.select: connection is required.'); + const sql = this.parseSQL(options.sql); + const db = this.getDbConnection(connection); + + if (!db) throw new Error(`Connection "${connection}" doesn't exist.`); + if (!sql) throw new Error('dbconnector.select: sql is required.'); + if (!sql.table) throw new Error('dbconnector.select: sql.table is required.'); + if (typeof sql.sort != 'string') sql.sort = this.parseOptional('{{ $_GET.sort }}', 'string', null); + if (typeof sql.dir != 'string') sql.dir = this.parseOptional('{{ $_GET.dir }}', 'string', 'asc'); + + if (sql.sort && sql.columns) { + if (!sql.orders) sql.orders = []; + + for (let column of sql.columns) { + if (column.column == sql.sort || column.alias == sql.sort) { + let order = { + column: column.alias || column.column, + direction: sql.dir.toLowerCase() == 'desc' ? 'desc' : 'asc' + }; + + if (column.table) order.table = column.table; + + sql.orders.unshift(order); + + break; + } + } + } + + sql.type = 'select'; + + if (db.client == 'couchdb') { + let table = sql.table.name || sql.table; + let { rows } = await db.list({ include_docs: true, startkey: table + '/', endkey: table + '0' }); + + rows = rows.map(row => row.doc); + + if (sql.wheres) { + const validate = (row, rule) => { + if (rule.operator) { + let a = row[rule.data.column]; + let b = rule.value; + + switch (rule.operator) { + case 'equal': return a == b; + case 'not_equal': return a != b; + case 'in': return b.includes(a); + case 'not_in': return !b.includes(a); + case 'less': return a < b; + case 'less_or_equal': return a <= b; + case 'greater': return a > b; + case 'greater_or_equal': return a >= b; + case 'between': return b[0] <= a <= b[1]; + case 'not_between': return !(b[0] <= a <= b[1]); + case 'begins_with': return String(a).startsWith(String(b)); + case 'not_begins_with': return !String(a).startsWith(String(b)); + case 'contains': return String(a).includes(String(b)); + case 'not_contains': return !String(a).includes(String(b)); + case 'ends_with': return String(a).endsWith(String(b)); + case 'not_ends_with': return !String(a).endsWith(String(b)); + case 'is_empty': return a == null || a == ''; + case 'is_not_empty': return a != null && a != ''; + case 'is_null': return a == null; + case 'is_not_null': return a != null; + } + } + + if (rule.condition && rule.rules.length) { + for (const _rule of rule.rules) { + const valid = validate(row, _rule); + if (!valid && rule.condition == 'AND') return false; + if (valid && rule.condition == 'OR') return true; + } + + return rule.condition == 'OR' ? false : true; + } + + return true; + }; + + rows = rows.filter(row => { + return validate(row, sql.wheres); + }); + } + + if (sql.orders && sql.orders.length) { + rows.sort((a, b) => { + for (let order of sql.orders) { + if (a[order.column] == b[order.column]) continue; + let desc = order.direction && order.direction.toLowerCase() == 'desc'; + if (a[order.column] < b[order.column]) { + return desc ? 1 : -1; + } else { + return desc ? -1 : 1; + } + } + + return 0; + }); + } + + if (sql.columns && sql.columns.length) { + // we can also skip if user want just all columns + if (!(sql.columns.length == 1 && sql.columns[0].column == '*')) { + rows = rows.map(doc => { + const row = {}; + + for (let column of sql.columns) { + if (column.column == '*') { + Object.assign(row, doc); + } else { + // only support single level for now + row[column.alias || column.column || column] = doc[column.column || column]; + } + } + + return row; + }); + } + } + + if (sql.distinct) { + rows = [...new Set(rows)]; + } + + let offset = Number(sql.offset || 0); + let limit = Number(sql.limit || 0); + return rows.slice(offset, limit ? offset + limit : undefined); + } + + if (options.test) { + return { + options: options, + query: db.fromJSON(sql, meta).toSQL().toNative() + }; + } + + if (hasSubs(sql)) { + prepareColumns(sql); + + const results = await db.fromJSON(sql, meta); + + if (results.length) { + if (sql.sub) { + await _processSubQueries.call(this, db, results, sql.sub, meta); + } + + if (sql.joins && sql.joins.length) { + for (const join of sql.joins) { + if (join.sub) { + await _processSubQueries.call(this, db, results, join.sub, meta, '_' + (join.alias || join.table)); + } + } + } + + cleanupResults(results); + } + + return results; + } + + return db.fromJSON(sql, meta); + }, + + count: async function(options) { + const connection = this.parseRequired(options.connection, 'string', 'dbconnector.count: connection is required.'); + const sql = this.parseSQL(options.sql); + const db = this.getDbConnection(connection); + + if (!db) throw new Error(`Connection "${connection}" doesn't exist.`); + if (!sql) throw new Error('dbconnector.count: sql is required.'); + if (!sql.table) throw new Error('dbconnector.count: sql.table is required.'); + + sql.type = 'count'; + + if (db.client == 'couchdb') { + let table = sql.table.name || sql.table; + let { rows } = await db.list({ include_docs: true, startkey: table + '/', endkey: table + '0' }); + + rows = rows.map(row => row.doc); + + if (sql.wheres) { + const validate = (row, rule) => { + if (rule.operator) { + let a = row[rule.data.column]; + let b = rule.value; + + switch (rule.operator) { + case 'equal': return a == b; + case 'not_equal': return a != b; + case 'in': return b.includes(a); + case 'not_in': return !b.includes(a); + case 'less': return a < b; + case 'less_or_equal': return a <= b; + case 'greater': return a > b; + case 'greater_or_equal': return a >= b; + case 'between': return b[0] <= a <= b[1]; + case 'not_between': return !(b[0] <= a <= b[1]); + case 'begins_with': return String(a).startsWith(String(b)); + case 'not_begins_with': return !String(a).startsWith(String(b)); + case 'contains': return String(a).includes(String(b)); + case 'not_contains': return !String(a).includes(String(b)); + case 'ends_with': return String(a).endsWith(String(b)); + case 'not_ends_with': return !String(a).endsWith(String(b)); + case 'is_empty': return a == null || a == ''; + case 'is_not_empty': return a != null && a != ''; + case 'is_null': return a == null; + case 'is_not_null': return a != null; + } + } + + if (rule.condition && rule.rules.length) { + for (const _rule of rule.rules) { + const valid = validate(row, _rule); + if (!valid && rule.condition == 'AND') return false; + if (valid && rule.condition == 'OR') return true; + } + + return rule.condition == 'OR' ? false : true; + } + + return true; + }; + + rows = rows.filter(row => { + return validate(row, sql.wheres); + }); + } + + if (sql.distinct) { + rows = [...new Set(rows)]; + } + + return rows.length; + } + + if (options.test) { + return { + options: options, + query: db.fromJSON(sql, meta).toSQL().toNative() + }; + } + + return (await db.fromJSON(sql)).Total; + }, + + single: async function(options, name, meta) { + const connection = this.parseRequired(options.connection, 'string', 'dbconnector.single: connection is required.'); + const sql = this.parseSQL(options.sql); + const db = this.getDbConnection(connection); + + if (!db) throw new Error(`Connection "${connection}" doesn't exist.`); + if (!sql) throw new Error('dbconnector.single: sql is required.'); + if (!sql.table) throw new Error('dbconnector.single: sql.table is required.'); + if (typeof sql.sort != 'string') sql.sort = this.parseOptional('{{ $_GET.sort }}', 'string', null); + if (typeof sql.dir != 'string') sql.dir = this.parseOptional('{{ $_GET.dir }}', 'string', 'asc'); + + sql.type = 'first'; + + if (db.client == 'couchdb') { + let table = sql.table.name || sql.table; + let { rows } = await db.list({ include_docs: true, startkey: table + '/', endkey: table + '0' }); + + rows = rows.map(row => row.doc); + + if (sql.wheres) { + const validate = (row, rule) => { + if (rule.operator) { + let a = row[rule.data.column]; + let b = rule.value; + + switch (rule.operator) { + case 'equal': return a == b; + case 'not_equal': return a != b; + case 'in': return b.includes(a); + case 'not_in': return !b.includes(a); + case 'less': return a < b; + case 'less_or_equal': return a <= b; + case 'greater': return a > b; + case 'greater_or_equal': return a >= b; + case 'between': return b[0] <= a <= b[1]; + case 'not_between': return !(b[0] <= a <= b[1]); + case 'begins_with': return String(a).startsWith(String(b)); + case 'not_begins_with': return !String(a).startsWith(String(b)); + case 'contains': return String(a).includes(String(b)); + case 'not_contains': return !String(a).includes(String(b)); + case 'ends_with': return String(a).endsWith(String(b)); + case 'not_ends_with': return !String(a).endsWith(String(b)); + case 'is_empty': return a == null || a == ''; + case 'is_not_empty': return a != null && a != ''; + case 'is_null': return a == null; + case 'is_not_null': return a != null; + } + } + + if (rule.condition && rule.rules.length) { + for (const _rule of rule.rules) { + const valid = validate(row, _rule); + if (!valid && rule.condition == 'AND') return false; + if (valid && rule.condition == 'OR') return true; + } + + return rule.condition == 'OR' ? false : true; + } + + return true; + }; + + rows = rows.filter(row => { + return validate(row, sql.wheres); + }); + } + + if (sql.orders && sql.orders.length) { + rows.sort((a, b) => { + for (let order of sql.orders) { + if (a[order.column] == b[order.column]) continue; + let desc = order.direction && order.direction.toLowerCase() == 'desc'; + if (a[order.column] < b[order.column]) { + return desc ? 1 : -1; + } else { + return desc ? -1 : 1; + } + } + + return 0; + }); + } + + if (sql.columns && sql.columns.length) { + // we can also skip if user want just all columns + if (!(sql.columns.length == 1 && sql.columns[0].column == '*')) { + rows = rows.map(doc => { + const row = {}; + + for (let column of sql.columns) { + if (column.column == '*') { + Object.assign(row, doc); + } else { + // only support single level for now + row[column.alias || column.column || column] = doc[column.column || column]; + } + } + + return row; + }); + } + } + + if (sql.distinct) { + rows = [...new Set(rows)]; + } + + return rows.length ? rows[0] : null; + } + + if (options.test) { + return { + options: options, + query: db.fromJSON(sql, meta).toSQL().toNative() + }; + } + + if (hasSubs(sql)) { + prepareColumns(sql); + + const result = await db.fromJSON(sql, meta); + + if (!result) return null; + + if (sql.sub) { + await _processSubQueries.call(this, db, [result], sql.sub, meta); + } + + if (sql.joins && sql.joins.length) { + for (const join of sql.joins) { + if (join.sub) { + await _processSubQueries.call(this, db, [result], join.sub, meta, '_' + (join.alias || join.table)); + } + } + } + + cleanupResults([result]); + + return result; + } + + return db.fromJSON(sql, meta) || null; + }, + + paged: async function(options, name, meta) { + const connection = this.parseRequired(options.connection, 'string', 'dbconnector.paged: connection is required.'); + const sql = this.parseSQL(options.sql); + const db = this.getDbConnection(connection); + + if (!db) throw new Error(`Connection "${connection}" doesn't exist.`); + if (!sql) throw new Error('dbconnector.paged: sql is required.'); + if (!sql.table) throw new Error('dbconnector.paged: sql.table is required.'); + if (typeof sql.offset != 'number') sql.offset = Number(this.parseOptional('{{ $_GET.offset }}', '*', 0)); + if (typeof sql.limit != 'number') sql.limit = Number(this.parseOptional('{{ $_GET.limit }}', '*', 25)); + if (typeof sql.sort != 'string') sql.sort = this.parseOptional('{{ $_GET.sort }}', 'string', null); + if (typeof sql.dir != 'string') sql.dir = this.parseOptional('{{ $_GET.dir }}', 'string', 'asc'); + + if (sql.sort && sql.columns) { + if (!sql.orders) sql.orders = []; + + for (let column of sql.columns) { + if (column.column == sql.sort || column.alias == sql.sort) { + let order = { + column: column.alias || column.column, + direction: sql.dir.toLowerCase() == 'desc' ? 'desc' : 'asc' + }; + + if (column.table) order.table = column.table; + + sql.orders.unshift(order); + + break; + } + } + } + + if (db.client == 'couchdb') { + let table = sql.table.name || sql.table; + let { rows } = await db.list({ include_docs: true, startkey: table + '/', endkey: table + '0' }); + + rows = rows.map(row => row.doc); + + if (sql.wheres) { + const validate = (row, rule) => { + if (rule.operator) { + let a = row[rule.data.column]; + let b = rule.value; + + switch (rule.operator) { + case 'equal': return a == b; + case 'not_equal': return a != b; + case 'in': return b.includes(a); + case 'not_in': return !b.includes(a); + case 'less': return a < b; + case 'less_or_equal': return a <= b; + case 'greater': return a > b; + case 'greater_or_equal': return a >= b; + case 'between': return b[0] <= a <= b[1]; + case 'not_between': return !(b[0] <= a <= b[1]); + case 'begins_with': return String(a).startsWith(String(b)); + case 'not_begins_with': return !String(a).startsWith(String(b)); + case 'contains': return String(a).includes(String(b)); + case 'not_contains': return !String(a).includes(String(b)); + case 'ends_with': return String(a).endsWith(String(b)); + case 'not_ends_with': return !String(a).endsWith(String(b)); + case 'is_empty': return a == null || a == ''; + case 'is_not_empty': return a != null && a != ''; + case 'is_null': return a == null; + case 'is_not_null': return a != null; + } + } + + if (rule.condition && rule.rules.length) { + for (const _rule of rule.rules) { + const valid = validate(row, _rule); + if (!valid && rule.condition == 'AND') return false; + if (valid && rule.condition == 'OR') return true; + } + + return rule.condition == 'OR' ? false : true; + } + + return true; + }; + + rows = rows.filter(row => { + return validate(row, sql.wheres); + }); + } + + if (sql.orders && sql.orders.length) { + rows.sort((a, b) => { + for (let order of sql.orders) { + if (a[order.column] == b[order.column]) continue; + let desc = order.direction && order.direction.toLowerCase() == 'desc'; + if (a[order.column] < b[order.column]) { + return desc ? 1 : -1; + } else { + return desc ? -1 : 1; + } + } + + return 0; + }); + } + + if (sql.columns && sql.columns.length) { + // we can also skip if user want just all columns + if (!(sql.columns.length == 1 && sql.columns[0].column == '*')) { + rows = rows.map(doc => { + const row = {}; + + for (let column of sql.columns) { + if (column.column == '*') { + Object.assign(row, doc); + } else { + // only support single level for now + row[column.alias || column.column || column] = doc[column.column || column]; + } + } + + return row; + }); + } + } + + if (sql.distinct) { + rows = [...new Set(rows)]; + } + + let offset = Number(sql.offset || 0); + let limit = Number(sql.limit || 0); + let total = rows.length; + + return { + offset, + limit, + total, + page: { + offset: { + first: 0, + prev: offset - limit > 0 ? offset - limit : 0, + next: offset + limit < total ? offset + limit : offset, + last: (Math.ceil(total / limit) - 1) * limit + }, + current: Math.floor(offset / limit) + 1, + total: Math.ceil(total / limit) + }, + data: rows.slice(offset, limit ? offset + limit : undefined) + }; + } + + sql.type = 'count'; + let total = +(await db.fromJSON(sql, meta))['Total']; + + sql.type = 'select'; + let data = []; + + if (options.test) { + return { + options: options, + query: db.fromJSON(sql, meta).toSQL().toNative() + }; + } + + if (hasSubs(sql)) { + prepareColumns(sql); + + const results = await db.fromJSON(sql, meta); + + if (results.length) { + if (sql.sub) { + await _processSubQueries.call(this, db, results, sql.sub, meta); + } + + if (sql.joins && sql.joins.length) { + for (const join of sql.joins) { + if (join.sub) { + await _processSubQueries.call(this, db, results, join.sub, meta, '_' + (join.alias || join.table)); + } + } + } + + cleanupResults(results); + } + + data = results; + } else { + data = await db.fromJSON(sql, meta); + } + + return { + offset: sql.offset, + limit: sql.limit, + total, + page: { + offset: { + first: 0, + prev: sql.offset - sql.limit > 0 ? sql.offset - sql.limit : 0, + next: sql.offset + sql.limit < total ? sql.offset + sql.limit : sql.offset, + last: (Math.ceil(total / sql.limit) - 1) * sql.limit + }, + current: Math.floor(sql.offset / sql.limit) + 1, + total: Math.ceil(total / sql.limit) + }, + data + } + }, + +}; + +async function _processSubQueries(db, results, sub, meta, prefix = '') { + const lookup = new Map(); + const keys = new Set(); + + // get keys from results and create lookup table + // add initial sub field to results (empty array) + for (const result of results) { + const key = String(result['__dmxPrimary' + prefix]); + + if (lookup.has(key)) { + lookup.get(key).push(result); + } else { + lookup.set(key, [result]); + } + + keys.add(key); + + for (const field in sub) { + result[field] = []; + } + } + + for (const field in sub) { + const sql = this.parseSQL(sub[field]); + + sql.type = 'select'; + + prepareColumns(sql); + + let submeta = meta && meta.find(data => data.name == field); + if (submeta && submeta.sub) submeta = submeta.sub; + + // get all subresults with a single query + const subResults = await db.fromJSON(sql, submeta).whereIn(sql.key, Array.from(keys)); + + if (subResults.length) { + if (sql.sub) { + await _processSubQueries.call(this, db, subResults, sql.sub, submeta); + } + + if (sql.joins && sql.joins.length) { + for (const join of sql.joins) { + if (join.sub) { + await _processSubQueries.call(this, db, subResults, join.sub, submeta, '_' + (join.alias || join.table)); + } + } + } + + // map the sub results to the parent recordset + for (const subResult of subResults) { + const results = lookup.get(String(subResult['__dmxForeign'])); + + if (results) { + for (const result of results) { + result[field].push(subResult); + } + } + } + } + } + + // we don't need to return anything since all is updated by reference +} + +function hasSubs(sql) { + if (sql.sub) return true; + + if (sql.joins && sql.joins.length) { + for (const join of sql.joins) { + if (join.sub) return true; + } + } + + return false; +} + +function prepareColumns(sql) { + const table = sql.table.alias || sql.table.name || sql.table; + + if (!Array.isArray(sql.columns) || !sql.columns.length) { + sql.columns = [{ + table: table, + column: '*' + }]; + + if (Array.isArray(sql.joins) && sql.joins.length) { + for (join of sql.joins) { + sql.columns.push({ + table: join.alias || join.table, + column: '*' + }); + } + } + } + + if (sql.sub && sql.primary) { + sql.columns.push({ + table: table, + column: sql.primary, + alias: '__dmxPrimary' + }); + + if (sql.groupBy && sql.groupBy.length) { + sql.groupBy.push({ + table: table, + column: sql.primary + }); + } + } + + if (sql.key) { + sql.columns.push({ + table: table, + column: sql.key, + alias: '__dmxForeign' + }); + + if (sql.groupBy && sql.groupBy.length) { + sql.groupBy.push({ + table: table, + column: sql.key + }); + } + } + + if (sql.joins && sql.joins.length) { + for (const join of sql.joins) { + if (join.sub && join.primary) { + sql.columns.push({ + table: join.alias || join.table, + column: join.primary, + alias: '__dmxPrimary_' + (join.alias || join.table) + }); + + if (sql.groupBy && sql.groupBy.length) { + sql.groupBy.push({ + table: join.alias || join.table, + column: join.primary + }); + } + } + } + } +} + +function cleanupResults(results) { + for (const result of results) { + for (const field of Object.keys(result)) { + if (field.startsWith('__dmx')) { + delete result[field]; + } else if (Array.isArray(result[field])) { + cleanupResults(result[field]); + } + } + } +} \ No newline at end of file diff --git a/lib/modules/dbupdater.js b/lib/modules/dbupdater.js new file mode 100644 index 0000000..5fc60f3 --- /dev/null +++ b/lib/modules/dbupdater.js @@ -0,0 +1,412 @@ +module.exports = { + + insert: async function(options) { + const connection = this.parseRequired(options.connection, 'string', 'dbconnector.insert: connection is required.'); + const sql = this.parseSQL(options.sql); + const db = this.getDbConnection(connection); + + if (!db) throw new Error(`Connection "${connection}" doesn't exist.`); + if (!sql) throw new Error('dbconnector.insert: sql is required.'); + if (!sql.table) throw new Error('dbconnector.insert: sql.table is required.'); + + sql.type = 'insert'; + + if (db.client == 'couchdb') { + const doc = {}; + + for (const value of sql.values) { + doc[value.column] = value.value; + } + + const result = await db.insert(doc, sql.table + '/' + Date.now()); + + if (result.ok) { + return { affected: 1, identity: result.id }; + } else { + //throw new Error('dbconnector.insert: error inserting document into couchdb.'); + return { affected: 0 }; + } + } + + if (options.test) { + return { + options: options, + query: sql.toString() + }; + } + + if (sql.sub) { + return db.transaction(async trx => { + // TODO: test how identity is returned for each database + // main insert, returns inserted id + const [identity] = (await trx.fromJSON(sql)).map(value => value[sql.returning] || value); + + // loop sub (relation table) + for (let { table, key, value, values } of Object.values(sql.sub)) { + if (!Array.isArray(value)) break; + + for (const current of value) { + if (typeof current == 'object') { + current[key] = identity; + await trx(table).insert(current); + } else { + if (values.length != 1) throw new Error('Invalid value mapping'); + await trx(table).insert({ + [key]: identity, + [values[0].column]: current + }); + } + } + } + + return { affected: 1, identity }; + }); + } + + let identity = await db.fromJSON(sql); + + if (identity) { + if (Array.isArray(identity)) { + identity = identity[0]; + } + + if (typeof identity == 'object') { + identity = identity[Object.keys(identity)[0]]; + } + } + + return { affected: 1, identity }; + }, + + update: async function(options) { + const connection = this.parseRequired(options.connection, 'string', 'dbconnector.update: connection is required.'); + const sql = this.parseSQL(options.sql); + const db = this.getDbConnection(connection); + + if (!db) throw new Error(`Connection "${connection}" doesn't exist.`); + if (!sql) throw new Error('dbconnector.update: sql is required.'); + if (!sql.table) throw new Error('dbconnector.update: sql.table is required.'); + + sql.type = 'update'; + + if (db.client == 'couchdb') { + let { rows } = await db.list({ include_docs: true, startkey: sql.table + '/', endkey: sql.table + '0' }); + + rows = rows.map(row => row.doc); + + if (sql.wheres) { + const validate = (row, rule) => { + if (rule.operator) { + let a = row[rule.data.column]; + let b = rule.value; + + switch (rule.operator) { + case 'equal': return a == b; + case 'not_equal': return a != b; + case 'in': return b.includes(a); + case 'not_in': return !b.includes(a); + case 'less': return a < b; + case 'less_or_equal': return a <= b; + case 'greater': return a > b; + case 'greater_or_equal': return a >= b; + case 'between': return b[0] <= a <= b[1]; + case 'not_between': return !(b[0] <= a <= b[1]); + case 'begins_with': return String(a).startsWith(String(b)); + case 'not_begins_with': return !String(a).startsWith(String(b)); + case 'contains': return String(a).includes(String(b)); + case 'not_contains': return !String(a).includes(String(b)); + case 'ends_with': return String(a).endsWith(String(b)); + case 'not_ends_with': return !String(a).endsWith(String(b)); + case 'is_empty': return a == null || a == ''; + case 'is_not_empty': return a != null && a != ''; + case 'is_null': return a == null; + case 'is_not_null': return a != null; + } + } + + if (rule.condition && rule.rules.length) { + for (const _rule of rule.rules) { + const valid = validate(row, _rule); + if (!valid && rule.condition == 'AND') return false; + if (valid && rule.condition == 'OR') return true; + } + + return rule.condition == 'OR' ? false : true; + } + + return true; + }; + + rows = rows.filter(row => { + return validate(row, sql.wheres); + }); + } + + const result = await db.bulk(rows.map(doc => { + for (const value of sql.values) { + doc[value.column] = value.value; + } + + return doc; + })); + + return { affected: Array.isArray(result) ? result.filter(result => result.ok).length : rows.length }; + } + + if (options.test) { + return { + options: options, + query: sql.toString() + }; + } + + if (sql.sub) { + return db.transaction(async trx => { + let updated = await trx.fromJSON(sql); + + if (!Array.isArray(updated)) { + // check if is single update + const single = ( + sql.wheres && + sql.wheres.rules && + sql.wheres.rules.length == 1 && + sql.wheres.rules[0].field == sql.returning && + sql.wheres.rules[0].operation == '=' + ); + + if (single) { + // get id from where condition + updated = [sql.wheres.rules[0].value]; + } else { + // create a select with same where conditions + updated = await trx.fromJSON({ + ...sql, + type: 'select', + columns: [sql.returning] + }); + + updated = updated.map(value => value[sql.returning]); + } + } else { + updated = updated.map(value => value[sql.returning]); + } + + // loop sub + for (let { table, key, value, values } of Object.values(sql.sub)) { + if (!Array.isArray(value)) continue; + + // delete old related data first + await trx(table).whereIn(key, updated).del(); + + // for each updated item + for (const identity of updated) { + // insert value + for (const current of value) { + if (typeof current == 'object') { + current[key] = identity; + await trx(table).insert(current); + } else { + if (values.length != 1) throw new Error('Invalid value mapping'); + await trx(table).insert({ + [key]: identity, + [values[0].column]: current + }); + } + } + } + } + + return { affected: updated.length }; + }); + } + + let affected = await db.fromJSON(sql); + + return { affected }; + }, + + delete: async function(options) { + const connection = this.parseRequired(options.connection, 'string', 'dbconnector.delete: connection is required.'); + const sql = this.parseSQL(options.sql); + const db = this.getDbConnection(connection); + + if (!db) throw new Error(`Connection "${connection}" doesn't exist.`); + if (!sql) throw new Error('dbconnector.delete: sql is required.'); + if (!sql.table) throw new Error('dbconnector.delete: sql.table is required.'); + + sql.type = 'del'; + + if (db.client == 'couchdb') { + let { rows } = await db.list({ include_docs: true, startkey: sql.table + '/', endkey: sql.table + '0' }); + + rows = rows.map(row => row.doc); + + if (sql.wheres) { + const validate = (row, rule) => { + if (rule.operator) { + let a = row[rule.data.column]; + let b = rule.value; + + switch (rule.operator) { + case 'equal': return a == b; + case 'not_equal': return a != b; + case 'in': return b.includes(a); + case 'not_in': return !b.includes(a); + case 'less': return a < b; + case 'less_or_equal': return a <= b; + case 'greater': return a > b; + case 'greater_or_equal': return a >= b; + case 'between': return b[0] <= a <= b[1]; + case 'not_between': return !(b[0] <= a <= b[1]); + case 'begins_with': return String(a).startsWith(String(b)); + case 'not_begins_with': return !String(a).startsWith(String(b)); + case 'contains': return String(a).includes(String(b)); + case 'not_contains': return !String(a).includes(String(b)); + case 'ends_with': return String(a).endsWith(String(b)); + case 'not_ends_with': return !String(a).endsWith(String(b)); + case 'is_empty': return a == null || a == ''; + case 'is_not_empty': return a != null && a != ''; + case 'is_null': return a == null; + case 'is_not_null': return a != null; + } + } + + if (rule.condition && rule.rules.length) { + for (const _rule of rule.rules) { + const valid = validate(row, _rule); + if (!valid && rule.condition == 'AND') return false; + if (valid && rule.condition == 'OR') return true; + } + + return rule.condition == 'OR' ? false : true; + } + + return true; + }; + + rows = rows.filter(row => { + return validate(row, sql.wheres); + }); + } + + const result = await db.bulk(rows.map(doc => { + doc._deleted = true; + return doc; + })); + + return { affected: Array.isArray(result) ? result.filter(result => result.ok).length : rows.length }; + } + + if (options.test) { + return { + options: options, + query: sql.toString() + }; + } + + if (sql.sub) { + return db.transaction(async trx => { + const deleted = (await trx.fromJSON(sql)).map(value => value[sql.returning] || value); + + // loop sub + for (let { table, key } of Object.values(sql.sub)) { + // delete related data + await trx(table).whereIn(key, deleted).del(); + } + + return { affected: deleted.length }; + }); + } + + let affected = await db.fromJSON(sql); + + return { affected }; + }, + + custom: async function(options) { + const connection = this.parseRequired(options.connection, 'string', 'dbupdater.custom: connection is required.'); + const sql = this.parseSQL(options.sql); + const db = this.getDbConnection(connection); + + if (!db) throw new Error(`Connection "${connection}" doesn't exist.`); + if (!sql) throw new Error('dbconnector.custom: sql is required.'); + if (typeof sql.query != 'string') throw new Error('dbupdater.custom: sql.query is required.'); + if (!Array.isArray(sql.params)) throw new Error('dbupdater.custom: sql.params is required.'); + + if (db.client == 'couchdb') { + throw new Error('dbupdater.custom: couchdb is not supported.'); + } + + const params = []; + const query = sql.query.replace(/([:@][a-zA-Z_]\w*|\?)/g, param => { + if (param == '?') { + params.push(sql.params[params.length].value); + return '?'; + } + + let p = sql.params.find(p => p.name == param); + if (p) { + params.push(p.value); + return '?'; + } + + return param; + }); + + let results = await db.raw(query, params); + + if (db.client.config.client == 'mysql' || db.client.config.client == 'mysql2') { + results = results[0]; + } else if (db.client.config.client == 'postgres' || db.client.config.client == 'redshift') { + results = results.rows; + } + + return results; + }, + + execute: async function(options) { + const connection = this.parseRequired(options.connection, 'string', 'dbupdater.execute: connection is required.'); + const query = this.parseRequired(options.query, 'string', 'dbupdater.execute: query is required.'); + const params = this.parseOptional(options.params, 'object', []); + const db = this.getDbConnection(connection); + + if (!db) throw new Error(`Connection "${connection}" doesn't exist.`); + + if (db.client == 'couchdb') { + throw new Error('dbupdater.execute: couchdb is not supported.'); + } + + let results = await db.raw(query, params); + + if (db.client.config.client == 'mysql' || db.client.config.client == 'mysql2') { + results = results[0]; + } else if (db.client.config.client == 'postgres' || db.client.config.client == 'redshift') { + results = results.rows; + } + + return results; + }, + + // bulk insert + // values is array of objects with column(key) and value + // batchSize is number of inserts per query (multi insert statement) + bulkinsert: async function(options) { + const connection = this.parseRequired(options.connection, 'string', 'dbupdater.bulkinsert: connection is required.'); + const table = this.parseRequired(options.table, 'string', 'dbupdater.bulkinsert: table is required.'); + const values = this.parseRequired(options.values, 'object', 'dbupdater.bulkinsert: values is required.'); + const batchSize = this.parseOptional(options.batchSize, 'number', 100); + const db = this.getDbConnection(connection); + + if (db.client == 'couchdb') { + throw new Error('dbupdater.bulkinsert: couchdb is not supported.'); + } + + await db.transaction(async transaction => { + for (let i = 0; i < values.length; i += batchSize) { + let batch = values.slice(i, i + batchSize); + await transaction(table).insert(batch); + } + }); + }, + +}; \ No newline at end of file diff --git a/lib/modules/export.js b/lib/modules/export.js new file mode 100644 index 0000000..63bed26 --- /dev/null +++ b/lib/modules/export.js @@ -0,0 +1,109 @@ +const fs = require('fs-extra'); +const { toSystemPath } = require('../core/path'); + +module.exports = { + + csv: async function(options) { + let path = this.parse(options.path); + let data = this.parse(options.data); + let header = this.parse(options.header); + let delimiter = this.parse(options.delimiter); + let overwrite = this.parse(options.overwrite); + + if (typeof path != 'string') throw new Error('export.csv: path is required.'); + if (!Array.isArray(data) || !data.length) throw new Error ('export.csv: data is required.'); + + delimiter = typeof delimiter == 'string' ? delimiter : ','; + + if (delimiter == '\\t') delimiter = '\t'; + + const fd = await fs.open(toSystemPath(path), overwrite ? 'w' : 'wx'); + + if (header) { + await putcsv(fd, Object.keys(data[0]), delimiter); + } + + for (let row of data) { + await putcsv(fd, row, delimiter); + } + + await fs.close(fd); + + return path; + }, + + xml: async function(options) { + let path = this.parse(options.path); + let data = this.parse(options.data); + let root = this.parse(options.root); + let item = this.parse(options.item); + let overwrite = this.parse(options.overwrite); + + if (typeof path != 'string') throw new Error('export.xml: path is required.'); + if (!Array.isArray(data) || !data.length) throw new Error('export.xml: data is required.'); + + root = typeof root == 'string' ? root : 'export'; + item = typeof item == 'string' ? item : 'item'; + + const fd = await fs.open(toSystemPath(path), overwrite ? 'w' : 'wx'); + + await fs.write(fd, `<${root}>`); + for (let row of data) { + await fs.write(fd, `<${item}>`); + for (let prop in row) { + await fs.write(fd, `<${prop}>`); + } + await fs.write(fd, ``); + } + await fs.write(fd, ``); + + await fs.close(fd); + + return path; + }, + +}; + +async function putcsv(fd, data, delimiter) { + let str = ''; + + if (typeof data != 'object') { + throw new Error('putcsv: Invalid data.'); + } + + for (let prop in data) { + if (data.hasOwnProperty(prop)) { + let value = String(data[prop]); + + if (/["\n\r\t\s]/.test(value) || value.includes(delimiter)) { + let escaped = false; + + str += '"'; + + for (let i = 0; i < value.length; i++) { + if (value.charAt(i) == '\\') { + escaped = true; + } else if (!escaped && value.charAt(i) == '"') { + str += '"'; + } else { + escaped = false; + } + + str += value.charAt(i); + } + + str += '"'; + } else { + str += value; + } + + str += delimiter; + } + } + + if (!str) { + throw new Error('putcsv: No data.'); + } + + return fs.write(fd, str.substr(0, str.length - delimiter.length) + '\r\n'); +} \ No newline at end of file diff --git a/lib/modules/fs.js b/lib/modules/fs.js new file mode 100644 index 0000000..e3f0ebc --- /dev/null +++ b/lib/modules/fs.js @@ -0,0 +1,258 @@ +const fs = require('fs-extra'); +const { join, dirname, basename, extname } = require('path'); +const { toSystemPath, toAppPath, toSiteUrl, getUniqFile, parseTemplate } = require('../core/path'); +const { map } = require('../core/async'); + +module.exports = { + + download: async function(options) { + let path = toSystemPath(this.parseRequired(options.path, 'string', 'fs.download: path is required.')); + let filename = this.parseOptional(options.filename, 'string', basename(path)); + + if (fs.existsSync(path)) { + this.res.download(path, filename); + this.noOutput = true; + } else { + this.res.sendStatus(404); + } + }, + + exists: async function(options) { + let path = toSystemPath(this.parseRequired(options.path, 'string', 'fs.exists: path is required.')); + + if (await isFile(path)) { + if (options.then) { + await this.exec(options.then, true); + } + return true; + } else { + if (options.else) { + await this.exec(options.else, true); + } + return false; + } + }, + + direxists: async function(options) { + let path = toSystemPath(this.parseRequired(options.path, 'string', 'fs.direxists: path is required.')); + + if (await isDirectory(path)) { + if (options.then) { + await this.exec(options.then, true); + } + return true; + } else { + if (options.else) { + await this.exec(options.else, true); + } + return false; + } + }, + + createdir: async function(options) { + let path = toSystemPath(this.parseRequired(options.path, 'string', 'fs.createdir: path is required.')); + + await fs.ensureDir(path); + + return toAppPath(path); + }, + + removedir: async function(options) { + let path = toSystemPath(this.parseRequired(options.path, 'string', 'fs.removedir: path is required.')); + + await fs.remove(path); + + return toAppPath(path); + }, + + emptydir: async function(options) { + let path = toSystemPath(this.parseRequired(options.path, 'string', 'fs.emptydir: path is required.')); + + await fs.emptyDir(path) + + return toAppPath(path); + }, + + move: async function(options) { + let from = toSystemPath(this.parseRequired(options.from, 'string', 'fs.move: from is required.')); + let to = toSystemPath(this.parseRequired(options.to, 'string', 'fs.move: to is required.')); + let overwrite = this.parseOptional(options.overwrite, 'boolean', false); + let createdir = this.parseOptional(options.createdir, 'boolean', true); + + if (!fs.existsSync(to)) { + if (createdir) { + await fs.ensureDir(to); + } else { + throw new Error(`Destination path doesn't exists.`); + } + } + + to = join(to, basename(from)); + + if (!overwrite) { + to = getUniqFile(to); + } + + await fs.move(from, to, { overwrite: true }); + + return toAppPath(to); + }, + + rename: async function(options) { + let path = toSystemPath(this.parseRequired(options.path, 'string', 'fs.rename: path is required.')); + let template = this.parseRequired(options.template, 'string', 'fs.rename: template is required.'); + let overwrite = this.parseOptional(options.overwrite, 'boolean', false); + let to = parseTemplate(path, template); + + if (!overwrite && fs.existsSync(to)) { + throw new Error(`fs.rename: file "${to}" already exists.`); + } + + await fs.rename(path, to); + + return toAppPath(to); + }, + + copy: async function(options) { + let from = toSystemPath(this.parseRequired(options.from, 'string', 'fs.copy: from is required.')); + let to = toSystemPath(this.parseRequired(options.to, 'string', 'fs.copy: to is required.')); + let overwrite = this.parseOptional(options.overwrite, 'boolean', false); + let createdir = this.parseOptional(options.createdir, 'boolean', true); + + if (!fs.existsSync(to)) { + if (createdir) { + await fs.ensureDir(to); + } else { + throw new Error(`Destination path doesn't exist.`); + } + } + + to = join(to, basename(from)); + + if (!overwrite && fs.existsSync(to)) { + to = getUniqFile(to); + } + + await fs.copy(from, to); + + return toAppPath(to); + }, + + remove: async function(options) { + let path = toSystemPath(this.parseRequired(options.path, 'string', 'fs.remove: path is required.')); + + await fs.unlink(path); + + return true; + }, + + dir: async function(options) { + let path = toSystemPath(this.parseRequired(options.path, 'string', 'fs.dir: path is required.')); + let allowedExtensions = this.parseOptional(options.allowedExtensions, 'string', ''); + let showHidden = this.parseOptional(options.showHidden, 'boolean', false); + let includeFolders = this.parseOptional(options.includeFolders, 'boolean', false); + let folderSize = this.parseOptional(options.folderSize, 'string', 'none'); + let concurrency = this.parseOptional(options.concurrency, 'number', 4); + + folderSize = ['none', 'files', 'recursive'].includes(folderSize) ? folderSize : 'none'; + allowedExtensions = allowedExtensions ? allowedExtensions.split(/\s*,\s*/).map(ext => lowercase(ext[0] == '.' ? ext : '.' + ext)) : []; + + let files = await fs.readdir(path, { withFileTypes: true }); + + files = files.filter(entry => { + if (!includeFolders && entry.isDirectory()) return false; + if (!showHidden && entry.name[0] == '.') return false; + if (allowedExtensions.length && entry.isFile() && !allowedExtensions.includes(lowercase(extname(entry.name)))) return false; + return entry.isFile() || entry.isDirectory(); + }); + + // Fast parallel map + return map(files, async (entry) => { + let curr = join(path, entry.name); + let stat = await fs.stat(curr); + + if (folderSize != 'none' && entry.isDirectory()) { + stat.size = await calcSize(curr, folderSize == 'recursive', concurrency); + } + + return { + type: entry.isFile() ? 'file' : 'dir', + name: entry.name, + folder: toAppPath(dirname(curr)), + basename: basename(curr, extname(curr)), + extension: extname(curr), + path: toAppPath(curr), + url: toSiteUrl(curr), + size: stat.size, + created: stat.ctime, + accessed: stat.atime, + modified: stat.mtime + }; + }, concurrency); + }, + + stat: async function(options) { + let path = toSystemPath(this.parseRequired(options.path, 'string', 'fs.stat: path is required.')); + let folderSize = this.parseOptional(options.folderSize, 'string', 'none'); + let concurrency = this.parseOptional(options.concurrency, 'number', 4); + + folderSize = ['none', 'files', 'recursive'].includes(folderSize) ? folderSize : 'none'; + + let stat = await fs.stat(path); + + if (folderSize != 'none' && stat.isDirectory()) { + stat.size = await calcSize(path, folderSize == 'recursive', concurrency); + } + + return { + type: stat.isFile() ? 'file' : 'dir', + name: basename(path), + folder: toAppPath(dirname(path)), + basename: basename(path, extname(path)), + extension: extname(path), + path: toAppPath(path), + url: toSiteUrl(path), + size: stat.size, + created: stat.ctime, + accessed: stat.atime, + modified: stat.mtime + }; + }, + +}; + +function lowercase(str) { + return str.toLowerCase(); +} + +async function calcSize(folder, recursive, concurrency) { + let entries = await fs.readdir(folder); + + return map(entries, async (entry) => { + let stat = await fs.stat(join(folder, entry)); + + if (stat.isDirectory() && recursive) { + return calcSize(join(folder, entry), recursive, concurrency); + } + + return stat.size; + }, concurrency).then(arr => arr.reduce((size, curr) => size + curr, 0)); +}; + +async function isFile(path) { + try { + let stats = await fs.stat(path); + return stats.isFile(); + } catch (err) { + return false; + } +} + +async function isDirectory(path) { + try { + let stats = await fs.stat(path); + return stats.isDirectory(); + } catch (err) { + return false; + } +} \ No newline at end of file diff --git a/lib/modules/image.js b/lib/modules/image.js new file mode 100644 index 0000000..a76db50 --- /dev/null +++ b/lib/modules/image.js @@ -0,0 +1,505 @@ +const fs = require('fs-extra'); +const Sharp = require('sharp'); +const debug = require('debug')('server-connect:image'); +const { basename, extname, join } = require('path'); +const { toAppPath, toSystemPath, parseTemplate, getUniqFile } = require('../core/path'); + +const positions = { + 'center': 0, + 'centre': 0, + 'top': 1, + 'north': 1, + 'right': 2, + 'east': 2, + 'bottom': 3, + 'south': 3, + 'left': 4, + 'west': 4, + 'top right': 5, + 'right top': 5, + 'northeast': 5, + 'bottom right': 6, + 'right bottom': 6, + 'southeast': 6, + 'bottom left': 7, + 'left bottom': 7, + 'southwest': 7, + 'top left': 8, + 'left top': 8, + 'northwest': 8, + 'entropy': 16, + 'attention': 17 +}; + +function cw(w, meta) { + if (typeof w == 'string') { + if (/%$/.test(w)) { + w = meta.width * parseFloat(w) / 100; + } + } + + if (w < 0) { + w = meta.width + w; + } + + return parseInt(w); +} + +function ch(h, meta) { + if (typeof h == 'string') { + if (/%$/.test(h)) { + h = meta.height * parseFloat(h) / 100; + } + } + + if (h < 0) { + h = meta.height + h; + } + + return parseInt(h); +} + +function cx(x, w, meta) { + if (typeof x == 'string') { + switch (x) { + case 'left': + x = 0; + break; + case 'center': + x = (meta.width - w) / 2; + break; + case 'right': + x = meta.width - w; + break; + default: + if (/%$/.test(x)) { + x = (meta.width - w) * parseFloat(x) / 100; + } + } + } + + if (x < 0) { + x = meta.width - w + x; + } + + return parseInt(x); +} + +function cy(y, h, meta) { + if (typeof y == 'string') { + switch (y) { + case 'top': + y = 0; + break; + case 'middle': + y = (meta.height - h) / 2; + break; + case 'bottom': + y = meta.height - h; + break; + default: + if (/%$/.test(y)) { + y = (meta.height - h) * parseFloat(y) / 100; + } + } + } + + if (y < 0) { + y = meta.height - h + y; + } + + return parseInt(y); +} + +async function updateImage(sharp) { + sharp.image = Sharp(await sharp.image.toBuffer()); + sharp.metadata = await sharp.image.metadata(); +} + +module.exports = { + + getImageSize: async function (options) { + let path = toSystemPath(this.parseRequired(options.path, 'string', 'image.getImageSize: path is required.')); + + const image = Sharp(path); + const metadata = await image.metadata(); + + return { + width: metadata.width, + height: metadata.height + }; + }, + + load: async function (options, name) { + let path = toSystemPath(this.parseRequired(options.path, 'string', 'image.load: path is required.')); + let orient = this.parseOptional(options.autoOrient, 'boolean', false); + + this.req.image = this.req.image || {}; + this.req.image[name] = { name: basename(path), image: Sharp(path), metadata: null }; + + const sharp = this.req.image[name]; + if (orient) sharp.image.rotate(); + + await updateImage(sharp); + + return { + name: basename(path), + width: sharp.metadata.width, + height: sharp.metadata.height + }; + }, + + save: async function (options) { + const sharp = this.req.image[options.instance]; + if (!sharp) throw new Error(`image.save: instance "${options.instance} doesn't exist.`); + + let path = toSystemPath(this.parseRequired(options.path, 'string', 'image.save: path is required.')); + let format = this.parseOptional(options.format, 'string', 'jpeg').toLowerCase(); + let template = this.parseOptional(options.template, 'string', '{name}{ext}'); + let overwrite = this.parseOptional(options.overwrite, 'boolean', false); + let createPath = this.parseOptional(options.createPath, 'boolean', true); + let background = this.parseOptional(options.background, 'string', '#FFFFFF'); + let quality = this.parseOptional(options.quality, 'number', 75); + + if (!fs.existsSync(path)) { + if (createPath) { + await fs.ensureDir(path); + } else { + throw new Error(`image.save: path "${path}" doesn't exist.`); + } + } + + let file = join(path, sharp.name); + + if (template) { + file = parseTemplate(file, template); + } + + if (format == 'auto') { + switch (extname(file).toLowerCase()) { + case '.png': format = 'png'; break; + case '.gif': format = 'gif'; break; + case '.webp': format = 'webp'; break; + default: format = 'jpeg'; + } + } + + if (format == 'jpeg') { + sharp.image.flatten({ background }); + sharp.image.toFormat(format, { quality }); + } else if (format == 'webp') { + sharp.image.toFormat(format, { quality }); + } else { + sharp.image.toFormat(format); + } + + const data = await sharp.image.toBuffer(); + + file = file.replace(extname(file), '.' + format.replace('jpeg', 'jpg')); + + if (fs.existsSync(file)) { + if (overwrite) { + await fs.unlink(file); + } else { + file = getUniqFile(file); + } + } + + await fs.writeFile(file, data) //.toFile(file); + + return toAppPath(file); + }, + + resize: async function (options) { + const sharp = this.req.image[options.instance]; + if (!sharp) throw new Error(`image.resize: instance "${options.instance} doesn't exist.`); + + let width = this.parseOptional(cw(this.parse(options.width), sharp.metadata), 'number', null); + let height = this.parseOptional(ch(this.parse(options.height), sharp.metadata), 'number', null); + let upscale = this.parseOptional(options.upscale, 'boolean', false); + + if (isNaN(width)) width = null; + if (isNaN(height)) height = null; + + sharp.image.resize(width, height, { fit: width && height ? 'fill' : 'cover', withoutEnlargement: !upscale }); + + await updateImage(sharp); + }, + + crop: async function (options) { + const sharp = this.req.image[options.instance]; + if (!sharp) throw new Error(`image.crop: instance "${options.instance} doesn't exist.`); + + let width = this.parseRequired(cw(this.parse(options.width)), 'number', 'image.crop: width is required.'); + let height = this.parseRequired(ch(this.parse(options.height)), 'number', 'image.crop: height is required.'); + if (width > sharp.metadata.width) width = sharp.metadata.width; + if (height > sharp.metadata.height) height = sharp.metadata.height; + let left = this.parseRequired(cx(this.parse(options.x), width, sharp.metadata), 'number', 'image.crop: x is required.'); + let top = this.parseRequired(cy(this.parse(options.y), height, sharp.metadata), 'number', 'image.crop: y is required.'); + + sharp.image.extract({ left, top, width, height }); + + await updateImage(sharp); + }, + + cover: async function (options) { + const sharp = this.req.image[options.instance]; + if (!sharp) throw new Error(`image.cover: instance "${options.instance}" doesn't exist.`); + + let width = this.parseRequired(options.width, 'number', 'image.cover: width is required.'); + let height = this.parseRequired(options.height, 'number', 'image.cover: height is required.'); + // position: see positions object for options + let position = this.parseOptional(options.position, 'string', 'center'); + // kernel: 'nearest', 'cubic', 'mitchell', 'lanczos2', 'lanczos3' + let kernel = this.parseOptional(options.kernel, 'string', 'lanczos3'); + + position = positions[position] || 0; + + sharp.image.resize({ width, height, position, kernel }); + + await updateImage(sharp); + }, + + watermark: async function (options) { + const sharp = this.req.image[options.instance]; + if (!sharp) throw new Error(`image.watermark: instance "${options.instance} doesn't exist.`); + + let path = toSystemPath(this.parseRequired(options.path, 'string', 'image.watermark: path is required.')); + let image = Sharp(path); + let metadata = await image.metadata(); + let input = await image.toBuffer(); + let left = this.parseRequired(cx(this.parse(options.x), metadata.width, sharp.metadata), 'number', 'image.watermark: x is required.'); + let top = this.parseRequired(cy(this.parse(options.y), metadata.height, sharp.metadata), 'number', 'image.watermark: y is required.'); + + sharp.image.composite([{ input, left, top }]); + }, + + text: async function (options) { + const sharp = this.req.image[options.instance]; + if (!sharp) throw new Error(`image.text: instance "${options.instance} doesn't exist.`); + + let x = this.parse(options.x); + let y = this.parse(options.y); + let text = this.parseRequired(options.text, 'string', 'image.text: text is required.'); + let font = this.parseOptional(options.font, 'string', 'Verdana'); + let size = this.parseOptional(options.size, 'number', 24); + let color = this.parseOptional(options.color, 'string', '#ffffff'); + + let width = sharp.metadata.width; + let height = sharp.metadata.height; + let anchor = 'start'; + + switch (x) { + case 'left': + x = '0%'; + anchor = 'start'; + break; + case 'center': + x = '50%'; + anchor = 'middle'; + break; + case 'right': + x = '100%'; + anchor = 'end'; + break; + default: + if (x < 0) { + x = width - x; + anchor = 'end'; + } + } + + switch (y) { + case 'top': + y = size; + break; + case 'middle': + y = (height / 2) - (size / 2); + break; + case 'bottom': + y = height; + break; + default: + if (y < 0) { + y = height - size - y; + } + } + + let svg = ` + + + ${text} + + `; + + const input = await Sharp(Buffer.from(svg)).toBuffer(); + + sharp.image.composite([{ input, left: 0, top: 0 }]); + }, + + tiled: async function (options) { + const sharp = this.req.image[options.instance]; + if (!sharp) throw new Error(`image.tiled: instance "${options.instance} doesn't exist.`); + + let input = toSystemPath(this.parseRequired(options.path, 'string', 'image.tiled: path is required.')); + let padding = this.parseOptional(options.padding, 'number', 0); + + if (padding) { + input = await Sharp(input).extend({ + top: padding, left: padding, bottom: 0, right: 0, background: { r: 0, g: 0, b: 0, alpha: 0 } + }).toBuffer(); + } + + sharp.image.composite([{ input, left: 0, top: 0, tile: true }]); + }, + + flip: async function (options) { + const sharp = this.req.image[options.instance]; + if (!sharp) throw new Error(`image.flip: instance "${options.instance} doesn't exist.`); + + let horizontal = this.parseOptional(options.horizontal, 'boolean', false); + let vertical = this.parseOptional(options.vertical, 'boolean', false); + + if (horizontal) sharp.image.flop(); + if (vertical) sharp.image.flip(); + }, + + rotateLeft: async function (options) { + const sharp = this.req.image[options.instance]; + if (!sharp) throw new Error(`image.rotateLeft: instance "${options.instance} doesn't exist.`); + + sharp.image.rotate(-90); + + await updateImage(sharp); + }, + + rotateRight: async function (options) { + const sharp = this.req.image[options.instance]; + if (!sharp) throw new Error(`image.rotateRight: instance "${options.instance} doesn't exist.`); + + sharp.image.rotate(90); + + await updateImage(sharp); + }, + + smooth: async function (options) { + const sharp = this.req.image[options.instance]; + if (!sharp) throw new Error(`image.smooth: instance "${options.instance} doesn't exist.`); + + sharp.image.convolve({ + width: 3, + height: 3, + kernel: [ + 1, 1, 1, + 1, 1, 1, + 1, 1, 1 + ] + }); + }, + + blur: async function (options) { + const sharp = this.req.image[options.instance]; + if (!sharp) throw new Error(`image.blur: instance "${options.instance} doesn't exist.`); + + sharp.image.convolve({ + width: 3, + height: 3, + kernel: [ + 1, 2, 1, + 2, 4, 2, + 1, 2, 1 + ] + }); + }, + + sharpen: async function (options) { + const sharp = this.req.image[options.instance]; + if (!sharp) throw new Error(`image.sharpen: instance "${options.instance} doesn't exist.`); + + sharp.image.convolve({ + width: 3, + height: 3, + kernel: [ + 0, -2, 0, + -2, 15, -2, + 0, -2, 0 + ] + }); + }, + + meanRemoval: async function (options) { + const sharp = this.req.image[options.instance]; + if (!sharp) throw new Error(`image.meanRemoval: instance "${options.instance} doesn't exist.`); + + sharp.image.convolve({ + width: 3, + height: 3, + kernel: [ + -1, -1, -1, + -1, 9, -1, + -1, -1, -1 + ] + }); + }, + + emboss: async function (options) { + const sharp = this.req.image[options.instance]; + if (!sharp) throw new Error(`image.emboss: instance "${options.instance} doesn't exist.`); + + sharp.image.convolve({ + width: 3, + height: 3, + kernel: [ + -1, 0, -1, + 0, 4, 0, + -1, 0, -1 + ], + offset: 127 + }); + }, + + edgeDetect: async function (options) { + const sharp = this.req.image[options.instance]; + if (!sharp) throw new Error(`image.edgeDetect: instance "${options.instance} doesn't exist.`); + + sharp.image.convolve({ + width: 3, + height: 3, + kernel: [ + -1, -1, -1, + 0, 0, 0, + 1, 1, 1 + ], + offset: 127 + }); + }, + + grayscale: async function (options) { + const sharp = this.req.image[options.instance]; + if (!sharp) throw new Error(`image.grayscale: instance "${options.instance} doesn't exist.`); + + sharp.image.grayscale(); + }, + + sepia: async function (options) { + const sharp = this.req.image[options.instance]; + if (!sharp) throw new Error(`image.sepia: instance "${options.instance} doesn't exist.`); + + sharp.image.tint({ r: 112, g: 66, b: 20 }); + }, + + invert: async function (options) { + const sharp = this.req.image[options.instance]; + if (!sharp) throw new Error(`image.invert: instance "${options.instance} doesn't exist.`); + + sharp.image.negate(); + }, + +}; \ No newline at end of file diff --git a/lib/modules/import.js b/lib/modules/import.js new file mode 100644 index 0000000..34c9c9c --- /dev/null +++ b/lib/modules/import.js @@ -0,0 +1,129 @@ +const fs = require('fs-extra'); +const { toSystemPath } = require('../core/path'); +const { keysToLowerCase } = require('../core/util'); + +// simple inital implementation +// better implementation at: +// https://github.com/adaltas/node-csv-parse +// https://github.com/mafintosh/csv-parser +function parseCSV(csv, options) { + if (!csv) return []; + + if (csv.charCodeAt(0) === 0xFEFF) { + csv = csv.slice(1); + } + + let delimiter = options.delimiter.replace('\\t', '\t'); + let keys = options.fields; + let line = 1; + let data = []; + + if (options.header) { + keys = getcsv(); + + options.fields.forEach(field => { + if (keys.indexOf(field) == -1) { + throw new Error('parseCSV: ' + field + ' is missing in ' + options.path); + } + }); + + line++; + } + + let size = keys.length; + + while (csv.length) { + let values = getcsv(); + let o = {}; + + if (values.length != size) { + throw new Error('parseCSV: colomns do not match. keys: ' + size + ', values: ' + values.length + ' at line ' + line); + } + + for (let i = 0; i < size; i++) { + o[keys[i]] = values[i]; + } + + data.push(o); + + line++; + } + + return data; + + function getcsv() { + let data = [''], l = csv.length, + esc = false, escesc = false, + n = 0, i = 0; + + while (i < l) { + let s = csv.charAt(i); + + if (s == '\n') { + if (esc) { + data[n] += s; + } else { + i++; + break; + } + } else if (s == '\r') { + if (esc) { + data[n] += s; + } + } else if (s == delimiter) { + if (esc) { + data[n] += s; + } else { + data[++n] = ''; + esc = false; + escesc = false; + } + } else if (s == '"') { + if (escesc) { + data[n] += s; + escesc = false; + } + + if (esc) { + esc = false; + escesc = true; + } else { + esc = true; + escesc = false; + } + } else { + if (escesc) { + data[n] += '"'; + escesc = false; + } + + data[n] += s; + } + + i++; + } + + csv = csv.substr(i); + + return data; + } +} + +module.exports = { + + csv: async function(options) { + let path = this.parseRequired(options.path, 'string', 'export.csv: path is required.'); + let fields = this.parseOptional(options.fields, 'object', []); + let header = this.parseOptional(options.header, 'boolean', false); + let delimiter = this.parseOptional(options.delimiter, 'string', ','); + let csv = await fs.readFile(toSystemPath(path), 'utf8'); + + return parseCSV(csv, { fields, header, delimiter }); + }, + + xml: async function(options) { + // TODO: import.xml + throw new Error('import.xml: not implemented.'); + }, + +}; \ No newline at end of file diff --git a/lib/modules/jwt.js b/lib/modules/jwt.js new file mode 100644 index 0000000..6299f05 --- /dev/null +++ b/lib/modules/jwt.js @@ -0,0 +1,22 @@ +exports.sign = function(options, name) { + return this.setJSONWebToken(name, options) +}; + +exports.decode = function(options) { + const jwt = require('jsonwebtoken'); + return jwt.decode(this.parse(options.token), { complete: true }); +}; + +exports.verify = function(options) { + const jwt = require('jsonwebtoken'); + options = this.parse(options); + + try { + return jwt.verify(options.token, options.key, options); + } catch (error) { + const debug = require('debug')('server-connect:jwt'); + debug('jwt verify failed: %o', error); + if (options.throw) throw error; + return { error }; + } +}; \ No newline at end of file diff --git a/lib/modules/mail.js b/lib/modules/mail.js new file mode 100644 index 0000000..34721fe --- /dev/null +++ b/lib/modules/mail.js @@ -0,0 +1,92 @@ +const fs = require('fs-extra'); +const { getFilesArray, toSystemPath } = require('../core/path'); +const { basename, posix } = require('path'); +const { v4: uuidv4 } = require('uuid'); +const IMPORTANCE = { 0: 'low', 1: 'normal', 2: 'high' }; + +module.exports = { + + setup: function(options, name) { + if (!name) throw new Error('mail.setup has no name.'); + this.setMailer(name, options); + }, + + send: async function(options) { + let setup = this.getMailer(this.parseOptional(options.instance, 'string', 'system')); + let subject = this.parseRequired(options.subject, 'string', 'mail.send: subject is required.'); + let fromEmail = this.parseRequired(options.fromEmail, 'string', 'mail.send: fromEmail is required.'); + let fromName = this.parseOptional(options.fromName, 'string', ''); + let toEmail = this.parseRequired(options.toEmail, 'string', 'mail.send: toEmail is required.'); + let toName = this.parseOptional(options.toName, 'string', ''); + let replyTo = this.parseOptional(options.replyTo, 'string', ''); + let cc = this.parseOptional(options.cc, 'string', ''); + let bcc = this.parseOptional(options.bcc, 'string', ''); + let source = this.parseOptional(options.source, 'string', 'static'); // static, file + let contentType = this.parseOptional(options.contentType, 'string', 'text'); // text / html + let body = this.parseOptional(options.body, 'string', ''); + let bodyFile = this.parseOptional(options.bodyFile, 'string', ''); + let embedImages = this.parseOptional(options.embedImages, 'boolean', false); + let priority = IMPORTANCE[this.parseOptional(options.importance, 'number', 1)]; + let attachments = this.parseOptional(options.attachments, '*', []); // "/file.ext" / ["/file.ext"] / {path:"/file.ext"} / [{path:"/file.ext"}] + + let from = fromName ? `"${fromName}" <${fromEmail}>` : fromEmail; + let to = toName ? `"${toName}" <${toEmail}>` : toEmail; + let text = body; + let html = null; + + if (source == 'file') { + body = this.parse(await fs.readFile(toSystemPath(bodyFile), 'utf8')); + } + + if (attachments) { + attachments = getFilesArray(attachments).map((path) => ({ filename: basename(path), path })); + } + + if (contentType == 'html') { + html = body; + + if (embedImages) { + let cid = {}; + + html = html.replace(/(?:"|')([^"']+\.(jpg|png|gif))(?:"|')/gi, (m, url) => { + let path = toSystemPath(url); + + if (fs.existsSync(path)) { + if (!cid[path]) { + cid[path] = uuidv4(); + attachments.push({ + filename: basename(path), + path: path, + cid: cid[path] + }); + } + + return `"cid:${cid[path]}"`; + } else { + console.warn(`${path} not found`); + } + + return `"${url}"`; + }); + } + + if (this.req.get) { // we can only do this if we have a request to get our hostname + const hasProxy = !!this.req.get('x-forwarded-host'); + const host = hasProxy ? `${this.req.protocol}://${this.req.hostname}` : this.req.get('host'); + + html = html.replace(/(href|src)(?:\s*=\s*)(?:"|')([^"']+)(?:"|')/gi, (m, attr, url) => { + if (!url.includes(':')) { + url = posix.join(host, url); + } + + return `${attr}="${url}"`; + }); + } + } + + const nodemailer = require('nodemailer'); + let transport = nodemailer.createTransport(setup); + return transport.sendMail({ from, to, cc, bcc, replyTo, subject, html, text, priority, attachments }); + }, + +}; \ No newline at end of file diff --git a/lib/modules/metadata.js b/lib/modules/metadata.js new file mode 100644 index 0000000..9ebe541 --- /dev/null +++ b/lib/modules/metadata.js @@ -0,0 +1,484 @@ +const fs = require('fs-extra'); + +const imageTypes = ['PNG', 'GIF', 'BMP', 'JPEG', 'TIFF']; +const videoTypes = ['AVI', 'MP4', 'MOV', 'MKV', 'WEBM', 'OGV']; +const soundTypes = ['OGG', 'WAV', 'MP3', 'FLAC']; + +const read = async (path, offset, length) => { + const fp = await fs.open(path); + const buff = Buffer.alloc(length); + + await fs.read(fd, buff, 0, length, offset); + await fs.close(); + + return buff; +}; + + +const parser = { + + PNG: async (path, result) => { + const buff = await read(path, 18, 6); + result.width = buff.readUInt16BE(0); + result.height = buff.readUInt16BE(4); + }, + + GIF: async (path, result) => { + const buff = await read(path, 6, 4); + result.width = buff.readUInt16LE(0); + result.height = buff.readUInt16LE(2); + }, + + BMP: async (path, result) => { + const buff = await read(path, 18, 8); + result.width = buff.readUInt32LE(0); + result.height = buff.readUInt32LE(4); + }, + + JPEG: async (path, result) => { + const sof = [0xc0, 0xc1, 0xc2, 0xc3, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xcb, 0xcd, 0xce, 0xcf, 0xde]; + const buff = await read(path, 2, 64000); + + let pos = 0; + + while (buff[pos++] == 0xff) { + let marker = buff[pos++]; + let size = buff.readUInt16BE(pos); + + if (marker == 0xda) break; + + if (sof.includes(marker)) { + result.height = buff.readUInt16BE(pos + 3); + result.width = buff.readUInt16BE(pos + 5); + break; + } + + pos += size; + } + }, + + TIFF: async (path, result) => { + const buff = await read(path, 0, 64000); + const le = buff.toString('ascii', 0, 2) == 'II'; + let pos = 0; + const readUInt16 = () => { pos += 2; return buff[le ? 'readUInt16LE' : 'readUInt16BE'](pos - 2); } + const readUInt32 = () => { pos += 4; return buff[le ? 'readUInt32LE' : 'readUInt32BE'](pos - 4); } + + let offset = readUInt32(); + + while (pos < buff.length && offset > 0) { + let entries = readUInt16(offset); + let start = pos; + + for (let i = 0; i < entries; i++) { + let tag = readUInt16(); + let type = readUInt16(); + let length = readUInt32(); + let data = (type == 3) ? readUInt16() : readUInt32(); + if (type == 3) pos += 2; + + if (tag == 256) { + result.width = data; + } else if (tag == 257) { + result.height = data; + } + + if (result.width > 0 && result.height > 0) { + return; + } + } + + offset = readUInt32(); + pos += offset; + } + }, + + AVI: async (path, result) => { + const buff = await read(path, 0, 144); + result.width = buff.readUInt32LE(64); + result.height = buff.readUInt32LE(68); + result.duration = ~~(buff.readUInt32LE(128) / buff.readUInt32LE(132) * buff.readUInt32LE(140)); + }, + + MP4: async (path, result) => { + return parser.MOV(path, result); + }, + + MOV: async (path, result, pos = 0) => { + const buff = await read(path, 0, 64000); + + while (pos < buff.length) { + let size = buff.readUInt32BE(pos); + let name = buff.toString('ascii', pos + 4, 4); + + if (name == 'mvhd') { + let scale = buff.readUInt32BE(pos + 20); + let duration = buff.readUInt32BE(pos + 24); + result.duration = ~~(duration / scale); + } + + if (name == 'tkhd') { + let m0 = buff.readUInt32BE(pos + 48); + let m4 = buff.readUInt32BE(pos + 64); + let w = buff.readUInt32BE(pos + 84); + let h = buff.readUInt32BE(pos + 88); + if (w > 0 && h > 0) { + result.width = w / m0; + result.height = h / m4; + return; + } + } + + if (name == 'moov' || name == 'trak') { + await parser.MOV(path, pos + 8); + } + + pos += size; + } + }, + + WEBM: async (path, result) => { + return parser.EBML(path, result); + }, + + MKV: async (path, result) => { + return parser.EBML(path, result); + }, + + EBML: async (path, result) => { + const containers = ['\x1a\x45\xdf\xa3', '\x18\x53\x80\x67', '\x15\x49\xa9\x66', '\x16\x54\xae\x6b', '\xae', '\xe0']; + const buff = await read(path, 0, 64000); + + // TODO parse EBML + }, + + OGV: async (path, result) => { + return parser.OGG(path, result); + }, + + OGG: async (path, result) => { + const buff = await read(apth, 0, 64000); + let pos = 0, vorbis; + + while (buff.toString('ascii', pos, pos + 4) == 'OggS') { + let version = buff[pos + 4]; + let b = buff[pos + 5]; + let continuation = !!(b & 0x01); + let bos = !!(b & 0x02); + let eos = !!(b & 0x04); + let position = Number(buff.readBigUInt64LE(pos + 6)); + let serial = buff.readUInt32LE(pos + 14); + let pageNumber = buff.readUInt32LE(pos + 18); + let checksum = buff.readUInt32LE(pos + 22); + let pageSegments = buff[path + 26]; + let lacing = buff.slice(pos + 27, pos + 27 + pageSegments); + let pageSize = lacing.reduce((p, v) => p + v, 0); + let start = pos + 27 + pageSegments; + let pageHeader = buff.slice(start, start + 7); + + if (pageHeader.compare(Buffer.from([0x01, 'v', 'o', 'r', 'b', 'i', 's']))) { + vorbis = { serial, sampleRate: buff.readUInt32LE(start + 12) }; + } + + if (pageHeader.compare(Buffer.from([0x80, 't', 'h', 'e', 'o', 'r', 'a']))) { + let version = buff.slice(start + 7, start + 10); + result.width = buff.readUInt16BE(start + 10) << 4; + result.height = buff.readUInt16BE(start + 12) << 4; + + if (version >= 0x030200) { + let width = buff.slice(start + 14, start + 17); + let height = buff.slice(start + 17, start + 20); + + if (width <= result.width && width > result.width - 16 && height <= result.height && height > result.height - 16) { + result.width = width; + result.height = height; + } + } + } + + if (eos && vorbis && serail == vorbis.serial) { + result.duration = ~~(position / vorbis.sampleRate); + } + + pos = start + pageSize; + } + }, + + WAV: async (path, result) => { + const buff = await read(path, 0, 32); + let size = buff.readUInt32LE(4); + let rate = buff.readUInt32LE(28); + result.duration = ~~(size / rate); + }, + + MP3: async (path, result) => { + const versions = [2.5, 0, 2, 1]; + const layers = [0, 3, 2, 1]; + const bitrates = [ + [ // version 2.5 + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // reserved + [0, 8,16,24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160], // layer 3 + [0, 8,16,24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160], // layer 2 + [0,32,48,56, 64, 80, 96,112,128,144,160,176,192,224,256] // layer 1 + ], + [ // reserved + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // reserved + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // reserved + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // reserved + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] // reserved + ], + [ // version 2 + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // reserved + [0, 8,16,24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160], // layer 3 + [0, 8,16,24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160], // layer 2 + [0,32,48,56, 64, 80, 96,112,128,144,160,176,192,224,256] // layer 1 + ], + [ // version 1 + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], // reserved + [0,32,40,48, 56, 64, 80, 96,112,128,160,192,224,256,320], // layer 3 + [0,32,48,56, 64, 80, 96,112,128,160,192,224,256,320,384], // layer 2 + [0,32,64,96,128,160,192,224,256,288,320,352,384,416,448] // layer 1 + ] + ] + const srates = [ + [11025, 12000, 8000, 0], // mpeg 2.5 + [ 0, 0, 0, 0], // reserved + [22050, 24000, 16000, 0], // mpeg 2 + [44100, 48000, 32000, 0] // mpeg 1 + ] + const tsamples = [ + [0, 576, 1152, 384], // mpeg 2.5 + [0, 0, 0, 0], // reserved + [0, 576, 1152, 384], // mpeg 2 + [0, 1152, 1152, 384] // mpeg 1 + ]; + const slotSizes = [0, 1, 1, 4]; + const modes = ['stereo', 'joint_stereo', 'dual_channel', 'mono']; + const buff = await read(path, 0, 64000); + + let duration = 0; + let count = 0; + let skip = 0; + let pos = 0; + + while (pos < buff.length) { + let start = pos; + + if (buff.toString('ascii', pos, 4) == 'TAG+') { + skip += 227; + pos += 227; + } else if (buff.toString('ascii', pos, 3) == 'TAG') { + skip += 128; + pos += 128; + } else if (buff.toString('ascii', pos, 3) == 'ID3') { + let bytes = buff.readUInt32BE(pos + 6); + let size = 10 + (bytes[0] << 21 | bytes[1] << 14 | bytes[2] << 7 | bytes[3]); + skip += size; + pos += size; + } else { + let hdr = buff.slice(pos, pos + 4); + + while (pos < buff.length && !(hdr[0] == 0xff && (hdr[1] & 0xe0) == 0xe0)) { + pos++; + hdr = buff.slice(pos, pos + 4); + } + + let ver = (hdr[1] & 0x18) >> 3; + let lyr = (hdr[1] & 0x06) >> 1; + let pad = (hdr[2] & 0x02) >> 1; + let brx = (hdr[2] & 0xf0) >> 4; + let srx = (hdr[2] & 0x0c) >> 2; + let mdx = (hdr[3] & 0xc0) >> 6; + + let version = versions[ver]; + let layer = layers[lyr]; + let bitrate = bitrates[ver][lyr][brx] * 1000; + let samprate = srates[ver][srx]; + let samples = tsamples[ver][lyr]; + let slotSize = slotSizes[lyr]; + let mode = modes[mdx]; + let fsize = ~~(((samples / 8 * bitrate) / samprate) + (pad ? slotSize : 0)); + + count++; + + if (count == 1) { + if (layer != 3) { + pos += 2; + } else { + if (mode != 'mono') { + if (version == 1) { + pos += 32; + } else { + pos += 17; + } + } else { + if (version == 1) { + pos += 17; + } else { + pos += 9; + } + } + } + + if (buff.toString('ascii', pos, pos + 4) == 'Xing' && (buff.readUInt32BE(pos + 4) & 0x0001) == 0x0001) { + let totalFrames = buff.readUInt32BE(pos + 8); + duration = totalFrames * samples / samprate; + break; + } + } + + if (fsize < 1) break; + + pos = start + fsize; + + duration += (samples / samprate); + } + } + + result.duration = ~~duration; + }, + + FLAC: async (path, result) => { + const buff = await read(path, 18, 8); + let rate = (buff[0] << 12) | (buff[1] << 4) | ((buff[2] & 0xf0) >> 4); + let size = ((buff[3] & 0x0f) << 32) | (buff[4] << 24) | (buff[5] << 16) | (buff[6] << 8) | buff[7]; + result.duration = ~~(size / rate); + }, + +}; + +async function detect(path) { + const buff = await read(path, 0, 12); + + if (buff.slice(0, 8).compare(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))) { + return 'PNG'; + } + + if (buff.toString('ascii', 0, 3) == 'GIF') { + return 'GIF'; + } + + if (buff.toString('ascii', 0, 2) == 'BM') { + return 'BMP'; + } + + if (buff.slice(0, 2).compare(Buffer.from([0xff, 0xd8]))) { + return 'JPEG'; + } + + if (buff.toString('ascii', 0, 2) == 'II' && buff.readUInt16LE(2) == 42) { + return 'TIFF'; + } + + if (buff.toString('ascii', 0, 2) == 'MM' && buff.readUInt16BE(2) == 42) { + return 'TIFF'; + } + + if (buff.toString('ascii', 0, 4) == 'RIFF' && buff.toString('ascii', 8, 4) == 'AVI ') { + return 'AVI'; + } + + if (buff.toString('ascii', 4, 4) == 'ftyp') { + return 'MP4'; + } + + if (buff.toString('ascii', 4, 4) == 'moov') { + return 'MOV'; + } + + if (buff.slice(0, 4).compare(Buffer.from([0x1a, 0x45, 0xdf, 0xa3]))) { + // TODO detect MKV + + return 'EBML'; + } + + if (buff.toString('ascii', 0, 4) == 'OggS') { + // TODO detect OGV + + return 'OGG' + } + + if (buff.toString('ascii', 0, 4) == 'RIFF', buff.toString('ascii', 8, 4) == 'WAVE') { + return 'WAV'; + } + + if (buff.toString('ascii', 0, 3) == 'ID3' || (buf[0] == 0xff && (buff[1] & 0xe0))) { + return 'MP3'; + } + + if (buff.toString('ascii', 0, 4) == 'fLaC') { + return 'FLAC'; + } + + return null +} + +module.exports = { + + detect: async function(options) { + let path = this.parseRequired(options.path, 'string', 'metadata.detect: path is required.'); + + return detect(path); + }, + + isImage: async function(options) { + let path = this.parseRequired(options.path, 'string', 'metadata.isImage: path is required.'); + let type = await detect(path); + let cond = imageTypes.includes(type); + + if (cond) { + if (options.then) { + await this.exec(options.then, true); + } + } else if (options.else) { + await this.exec(options.else, true); + } + + return cond; + }, + + isVideo: async function(options) { + let path = this.parseRequired(options.path, 'string', 'metadata.isVideo: path is required.'); + let type = await detect(path); + let cond = videoTypes.includes(type); + + if (cond) { + if (options.then) { + await this.exec(options.then, true); + } + } else if (options.else) { + await this.exec(options.else, true); + } + + return cond; + }, + + isSound: async function(options) { + let path = this.parseRequired(options.path, 'string', 'metadata.isSound: path is required.'); + let type = await detect(path); + let cond = soundTypes.includes(type); + + if (cond) { + if (options.then) { + await this.exec(options.then, true); + } + } else if (options.else) { + await this.exec(options.else, true); + } + + return cond; + }, + + fileinfo: async function(options) { + let path = this.parseRequired(options.path, 'string', 'metadata.fileinfo: path is required.'); + let type = await detect(path); + let result = { type, width: null, height: null, duration: null }; + + if (parser[type]) { + await parser[type](path, result); + } + + return result; + }, + +}; \ No newline at end of file diff --git a/lib/modules/oauth.js b/lib/modules/oauth.js new file mode 100644 index 0000000..e9e94ee --- /dev/null +++ b/lib/modules/oauth.js @@ -0,0 +1,24 @@ +module.exports = { + + provider: async function(options, name) { + const oauth = await this.setOAuthProvider(name, options) + + return { + access_token: oauth.access_token, + refresh_token: oauth.refresh_token + }; + }, + + authorize: async function(options) { + const oauth = await this.getOAuthProvider(options.provider); + + return oauth.authorize(this.parse(options.scopes), this.parse(options.params)); + }, + + refresh: async function(options) { + const oauth = await this.getOAuthProvider(options.provider); + + return oauth.refreshToken(this.parse(options.refresh_token)); + }, + +}; \ No newline at end of file diff --git a/lib/modules/recaptcha.js b/lib/modules/recaptcha.js new file mode 100644 index 0000000..2f5266f --- /dev/null +++ b/lib/modules/recaptcha.js @@ -0,0 +1,47 @@ +const querystring = require('querystring'); +const https = require('https'); + +exports.validate = function(options) { + const secret = this.parseRequired(options.secret, 'string', 'recaptcha.validate: secret is required.'); + const msg = this.parseOptional(options.msg, 'string', 'Recaptcha check failed.'); + + const response = this.req.body['g-recaptcha-response']; + const remoteip = this.req.ip; + const data = querystring.stringify({ secret, response, remoteip }); + + return new Promise((resolve, reject) => { + const req = https.request('https://www.google.com/recaptcha/api/siteverify', { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Content-Length': data.length + } + }, (res) => { + let body = ''; + + res.setEncoding('utf8'); + res.on('data', (chunk) => body += chunk); + res.on('end', () => { + if (res.statusCode >= 400) return reject(body); + + if (body.charCodeAt(0) === 0xFEFF) { + body = body.slice(1); + } + + body = JSON.parse(body); + + if (!body.success) { + this.res.status(400).json({ + form: { 'g-recaptcha-response': msg } + }); + } + + resolve(body); + }); + }); + + req.on('error', reject); + req.write(data); + req.end(); + }); +}; \ No newline at end of file diff --git a/lib/modules/redis.js b/lib/modules/redis.js new file mode 100644 index 0000000..143f0b7 --- /dev/null +++ b/lib/modules/redis.js @@ -0,0 +1,237 @@ +const client = global.redisClient; + +if (client) { + const { promisify } = require('util'); + const commands = {}; + + [ + 'append', 'decr', 'decrby', 'del', 'exists', 'get', 'getset', 'incr', 'incrby', + 'mget', 'set', 'setex', 'setnx', 'strlen', 'lindex', 'linsert', 'llen', 'lpop', + 'lpos', 'lpush', 'lpushx', 'lrange', 'lrem', 'lset', 'ltrim', 'rpop', 'rpush', + 'rpushx', 'copy', 'del', 'expire', 'expireat', 'keys', 'persist', 'pexpire', + 'pexpireat', 'pttl', 'randomkey', 'rename', 'renamenx', 'touch', 'ttl', 'type', + 'unlink' + ].forEach(command => { + commands[command] = promisify(client[command]).bind(client); + }); + + exports.append = function(options) { + options = this.parse(options); + return commands.append(options.key, options.value); + }; + + exports.decr = function(options) { + options = this.parse(options); + return commands.decr(options.key); + }; + + exports.decrby = function(options) { + options = this.parse(options); + return commands.decrby(options.key, options.decrement); + }; + + exports.del = function(options) { + options = this.parse(options); + return commands.del(options.key); + }; + + exports.exists = function(options) { + options = this.parse(options); + return commands.exists(options.key); + }; + + exports.get = function(options) { + options = this.parse(options); + return commands.get(options.key); + }; + + exports.getset = function(options) { + options = this.parse(options); + return commands.getset(options.key, options.value); + }; + + exports.incr = function(options) { + options = this.parse(options); + return commands.incr(options.key); + }; + + exports.incrby = function(options) { + options = this.parse(options); + return commands.incrby(options.key, options.increment); + }; + + exports.mget = function(options) { + options = this.parse(options); + return commands.mget(options.keys); + }; + + exports.set = function(options) { + options = this.parse(options); + return commands.set(options.key, options.value); + }; + + exports.setex = function(options) { + options = this.parse(options); + return commands.setex(options.key, options.seconds, options.value); + }; + + exports.setnx = function(options) { + options = this.parse(options); + return commands.setnx(options.key, options.value); + }; + + exports.strlen = function(options) { + options = this.parse(options); + return commands.strlen(options.key); + }; + + exports.lindex = function(options) { + options = this.parse(options); + return commands.lindex(options.key, options.index); + }; + + exports.linsert = function(options) { + options = this.parse(options); // position: BEFORE|AFTER + return commands.linsert(options.key, options.position, options.pivot, options.element); + }; + + exports.llen = function(options) { + options = this.parse(options); + return commands.llen(options.key); + }; + + exports.lpop = function(options) { + options = this.parse(options); + return commands.lpop(options.key, options.count); + }; + + exports.lpos = function(options) { + options = this.parse(options); + return commands.lpos(options.key, options.element); + }; + + exports.lpush = function(options) { + options = this.parse(options); + return commands.lpush(options.key, options.element); + }; + + exports.lpushx = function(options) { + options = this.parse(options); + return commands.lpushx(options.key, options.element); + }; + + exports.lrange = function(options) { + options = this.parse(options); + return commands.lrange(options.key, options.start, options.stop); + }; + + exports.lrem = function(options) { + options = this.parse(options); + return commands.lrem(options.key, options.count, options.element); + }; + + exports.lset = function(options) { + options = this.parse(options); + return commands.lset(options.key, options.index, options.element); + }; + + exports.ltrim = function(options) { + options = this.parse(options); + return commands.ltrim(options.key, options.start, options.stop); + }; + + exports.rpop = function(options) { + options = this.parse(options); + return commands.rpop(options.key, options.count); + }; + + exports.rpush = function(options) { + options = this.parse(options); + return commands.rpush(options.key, options.element); + }; + + exports.rpushx = function(options) { + options = this.parse(options); + return commands.rpushx(options.key, options.element); + }; + + exports.copy = function(options) { + options = this.parse(options); + return commands.copy(options.source, options.destination); + }; + + exports.del = function(options) { + options = this.parse(options); + return commands.del(options.key); + }; + + exports.expire = function(options) { + options = this.parse(options); + return commands.expire(options.key, options.seconds); + }; + + exports.expireat = function(options) { + options = this.parse(options); + return commands.expireat(options.key, options.timestamp); + }; + + exports.keys = function(options) { + options = this.parse(options); + return commands.keys(options.pattern); + }; + + exports.persist = function(options) { + options = this.parse(options); + return commands.persist(options.key); + }; + + exports.pexpire = function(options) { + options = this.parse(options); + return commands.pexpire(options.key, options.milliseconds); + }; + + exports.pexpireat = function(options) { + options = this.parse(options); + return commands.pexpireat(options.key, options.mstimestamp) + }; + + exports.pttl = function(options) { + options = this.parse(options); + return commands.pttl(options.key); + }; + + exports.randomkey = function(options) { + options = this.parse(options); + return commands.randomkey(); + }; + + exports.rename = function(options) { + options = this.parse(options); + return commands.rename(options.key, options.newkey); + }; + + exports.renamenx = function(options) { + options = this.parse(options); + return commands.renamenx(options.key, options.newkey); + }; + + exports.touch = function(options) { + options = this.parse(options); + return commands.touch(options.key); + }; + + exports.ttl = function(options) { + options = this.parse(options); + return commands.ttl(options.key); + }; + + exports.type = function(options) { + options = this.parse(options); + return commands.type(options.key); + }; + + exports.unlink = function(options) { + options = this.parse(options); + return commands.unlink(options.key); + }; +} \ No newline at end of file diff --git a/lib/modules/s3.js b/lib/modules/s3.js new file mode 100644 index 0000000..bebda02 --- /dev/null +++ b/lib/modules/s3.js @@ -0,0 +1,174 @@ +const fs = require('fs-extra'); +const mime = require('mime-types'); +const { join, basename, dirname } = require('path'); +const { toSystemPath } = require('../core/path'); + +module.exports = { + + provider: function (options, name) { + this.setS3Provider(name, options); + }, + + createBucket: async function (options) { + const provider = this.parseRequired(options.provider, 'string', 's3.createBucket: provider is required.'); + const Bucket = this.parseRequired(options.bucket, 'string', 's3.createBucket: bucket is required.'); + const ACL = this.parseOptional(options.acl, 'string', undefined); + const s3 = this.getS3Provider(provider); + + if (!s3) throw new Error(`S3 provider "${provider}" doesn't exist.`); + + return s3.createBucket({ Bucket, ACL }); + }, + + listBuckets: async function (options) { + const provider = this.parseRequired(options.provider, 'string', 's3.listBuckets: provider is required.'); + const s3 = this.getS3Provider(provider); + + if (!s3) throw new Error(`S3 provider "${provider}" doesn't exist.`); + + return s3.listBuckets({}); + }, + + deleteBucket: async function (options) { + const provider = this.parseRequired(options.provider, 'string', 's3.deleteBucket: provider is required.'); + const Bucket = this.parseRequired(options.bucket, 'string', 's3.deleteBucket: bucket is required.'); + const s3 = this.getS3Provider(provider); + + if (!s3) throw new Error(`S3 provider "${provider}" doesn't exist.`); + + return s3.deleteBucket({ Bucket }); + }, + + listFiles: async function (options) { + const provider = this.parseRequired(options.provider, 'string', 's3.listFiles: provider is required.'); + const Bucket = this.parseRequired(options.bucket, 'string', 's3.listFiles: bucket is required.'); + const MaxKeys = this.parseOptional(options.maxKeys, 'number', undefined); + const Prefix = this.parseOptional(options.prefix, 'string', undefined); + const ContinuationToken = this.parseOptional(options.continuationToken, 'string', undefined); + const StartAfter = this.parseOptional(options.startAfter, 'string', undefined); + const s3 = this.getS3Provider(provider); + + if (!s3) throw new Error(`S3 provider "${provider}" doesn't exist.`); + + return s3.listObjectsV2({ Bucket, MaxKeys, Prefix, ContinuationToken, StartAfter }); + }, + + putFile: async function (options) { + const provider = this.parseRequired(options.provider, 'string', 's3.uploadFile: provider is required.'); + const Bucket = this.parseRequired(options.bucket, 'string', 's3.uploadFile: bucket is required.'); + const path = toSystemPath(this.parseRequired(options.path, 'string', 's3.uploadFile: path is required.')); + const Key = this.parseRequired(options.key, 'string', 's3.uploadFile: key is required.'); + const ContentType = this.parseOptional(options.contentType, 'string', mime.lookup(Key) || 'application/octet-stream'); + const ContentDisposition = this.parseOptional(options.contentDisposition, 'string', undefined); + const ACL = this.parseOptional(options.acl, 'string', undefined); + const s3 = this.getS3Provider(provider); + + if (!s3) throw new Error(`S3 provider "${provider}" doesn't exist.`); + + let Body = fs.createReadStream(path); + + const result = await s3.putObject({ Bucket, ACL, Key, ContentType, ContentDisposition, Body }); + + try { + const endpoint = await s3.config.endpoint(); + result.Location = `https://${Bucket}.${endpoint.hostname}/${Key}`; + } catch (e) {} + + + return result; + }, + + getFile: async function (options) { + const provider = this.parseRequired(options.provider, 'string', 's3.getFile: provider is required.'); + const Bucket = this.parseRequired(options.bucket, 'string', 's3.getFile: bucket is required.'); + const Key = this.parseRequired(options.key, 'string', 's3.getFile: key is required.'); + const path = toSystemPath(this.parseRequired(options.path, 'string', 's3.getFile: path is required.')); + const stripKeyPath = this.parseOptional(options.stripKeyPath, 'boolean', false); + const s3 = this.getS3Provider(provider); + + if (!s3) throw new Error(`S3 provider "${provider}" doesn't exist.`); + + const file = Key; + if (stripKeyPath) file = basename(file); + const destination = join(path, file); + + await fs.ensureDir(dirname(destination)); + + const writer = fs.createWriteStream(destination); + + const { Body } = await s3.getObject({ Bucket, Key }); + + Body.pipe(writer); + + return new Promise((resolve, reject) => { + writer.on('finish', resolve); + writer.on('error', reject); + }); + }, + + deleteFile: async function (options) { + const provider = this.parseRequired(options.provider, 'string', 's3.deleteFile: provider is required.'); + const Bucket = this.parseRequired(options.bucket, 'string', 's3.deleteFile: bucket is required.'); + const Key = this.parseRequired(options.key, 'string', 's3.deleteFile: key is required.'); + const s3 = this.getS3Provider(provider); + + if (!s3) throw new Error(`S3 provider "${provider}" doesn't exist.`); + + return s3.deleteObject({ Bucket, Key }); + }, + + downloadFile: async function (options) { + const provider = this.parseRequired(options.provider, 'string', 's3.downloadFile: profider is required.'); + const Bucket = this.parseRequired(options.bucket, 'string', 's3.downloadFile: bucket is required.'); + const Key = this.parseRequired(options.key, 'string', 's3.downloadFile: key is required.'); + const s3 = this.getS3Provider(provider); + + if (!s3) throw new Error(`S3 provider "${provider}" doesn't exist.`); + + const data = await s3.headObject({ Bucket, Key }); + + this.res.set('Content-Length', data.ContentLength); + this.res.attachment(basename(Key)); + + const { Body } = await s3.getObject({ Bucket, Key }); + + Body.pipe(this.res); + + this.noOutput = true; + }, + + signDownloadUrl: async function (options) { + const provider = this.parseRequired(options.provider, 'string', 's3.signDownloadUrl: provider is required.'); + const Bucket = this.parseRequired(options.bucket, 'string', 's3.signDownloadUrl: bucket is required.'); + const Key = this.parseRequired(options.key, 'string', 's3.signDownloadUrl: key is required.'); + const expiresIn = this.parseOptional(options.expires, 'number', 300); + const s3 = this.getS3Provider(provider); + + if (!s3) throw new Error(`S3 provider "${provider}" doesn't exist.`); + + const { getSignedUrl } = require("@aws-sdk/s3-request-presigner"); + const { GetObjectCommand } = require('@aws-sdk/client-s3'); + const command = new GetObjectCommand({ Bucket, Key }); + + return getSignedUrl(s3, command, { expiresIn }); + }, + + signUploadUrl: async function (options) { + const provider = this.parseRequired(options.provider, 'string', 's3.signUploadUrl: provider is required.'); + const Bucket = this.parseRequired(options.bucket, 'string', 's3.signUploadUrl: bucket is required.'); + const Key = this.parseRequired(options.key, 'string', 's3.signUploadUrl: key is required.'); + const ContentType = this.parseOptional(options.contentType, 'string', mime.lookup(Key) || 'application/octet-stream'); + const expiresIn = this.parseOptional(options.expires, 'number', 300); + const ACL = this.parseOptional(options.acl, 'string', undefined); + const s3 = this.getS3Provider(provider); + + if (!s3) throw new Error(`S3 provider "${provider}" doesn't exist.`); + + const { getSignedUrl } = require("@aws-sdk/s3-request-presigner"); + const { PutObjectCommand } = require('@aws-sdk/client-s3'); + const command = new PutObjectCommand({ Bucket, Key, ContentType, ACL }); + + return getSignedUrl(s3, command, { expiresIn }); + } + +}; \ No newline at end of file diff --git a/lib/modules/sockets.js b/lib/modules/sockets.js new file mode 100644 index 0000000..b03291e --- /dev/null +++ b/lib/modules/sockets.js @@ -0,0 +1,172 @@ +/** + * emits an event to all clients filtered by the given options + * @param {string} namespace - only to the specified namespace + * @param {string} room - emit only to the specified room (can be combined with namespace) + * @param {string} eventName - the name of the event + * @param {object} params - any parameters/data to send with the event + */ +exports.emit = function(options) { + if (this.io) { + options = this.parse(options); + + if (options.namespace) { + if (options.room) { + this.io.of(options.namespace).to(options.room).emit(options.eventName, options.params); + } else { + this.io.of(options.namespace).emit(options.eventName, options.params); + } + } else { + if (options.room) { + this.io.in(options.room).emit(options.eventName, options.params); + } else { + this.io.emit(options.eventName, options.params); + } + } + } +}; + +/** + * emit an event to all clients except the sender + * @param {string} room - broadcast in a specific room + * @param {string} eventName - the name of the event + * @param {object} params - the parameters/data to send with the event + */ +exports.broadcast = function(options) { + if (this.socket) { + options = this.parse(options); + + if (options.room) { + this.socket.to(options.room).emit(options.eventName, options.params); + } else { + this.socket.broadcast.emit(options.eventName, options.params); + } + } +}; + +/** + * emit an event to client and wait for an answer + * @param {string} room - broadcast in a specific room + * @param {string} eventName - the name of the event + * @param {object} params - the parameters/data to send with the event + */ +exports.request = function(options) { + if (this.socket) { + options = this.parse(options); + + return new Promise((resolve) => { + this.socket.emit(options.eventName, options.params, resolve); + }); + } + + return null; +}; + +/** + * send a private meessage to a socket + * @param {string} socketId - the socket id to send the message to + * @param {string} eventName - the name of the event + * @param {object} params - the parameters/data to send with the event + */ +exports.message = function(options) { + if (this.io) { + options = this.parse(options); + + this.io.to(options.socketId).emit(options.eventName, options.params); + } +}; + +/** + * special serverconnect refresh broadcast + * @param {string} action - the action that require refresh + */ +exports.refresh = async function(options) { + if (this.io) { + options = this.parse(options); + + // Do we have a global redis client? + if (global.redisClient) { + try { // ignore any errors here + let wsKeys = await global.redisClient.keys('ws:' + options.action + ':*'); + if (wsKeys.length) await global.redisClient.del(wsKeys); + let scKeys = await global.redisClient.keys('erc:' + '/api/' + options.action + '*'); + if (scKeys.length) await global.redisClient.del(scKeys); + } catch (e) { + console.error(e); + } + } + + this.io.of('/api').emit(options.action, options.params); + } +}; + +/** + * let current client join a room + * @param {string} room - the room to join + */ +exports.join = function(options) { + if (this.socket) { + this.socket.join(this.parse(options.room)); + } +}; + +/** + * let current client leave a room + * @param {string} room - the room to leave + */ +exports.leave = function(options) { + if (this.socket) { + this.socket.leave(this.parse(options.room)); + } +}; + +/** + * get the socket id of the current client + */ +exports.identify = function(options) { + return this.socket ? this.socket.id : null; +}; + +/** + * get the rooms the current client has joined + */ +exports.rooms = function(options) { + return this.socket ? Array.from(this.socket.rooms) : []; +}; + +/** + * get all the rooms + * @param {string} namespace - the namespace + */ +exports.allRooms = async function(options) { + if (this.io) { + let adapter = io.of(options.namespace || '/').adapter; + + if (typeof adapter.allRooms == 'function') { + return Array.from(await adapter.allRooms()); + } else if (adapter.rooms) { + return Array.from(adapter.rooms.keys()); + } + } + + return []; +}; + +/** + * get all the connected sockets + * @param {string} namespace - the namespace + * @param {string} room - return only clients in a specific room + */ +exports.allSockets = async function(options) { + if (this.io) { + options = this.parse(options); + + if (options.room) { + return Array.from(await this.io.of(options.namespace || '/').in(options.room).allSockets()); + } else { + return Array.from(await this.io.of(options.namespace || '/').allSockets()); + } + } + + return []; +}; + diff --git a/lib/modules/stripe.js b/lib/modules/stripe.js new file mode 100644 index 0000000..b7b44ae --- /dev/null +++ b/lib/modules/stripe.js @@ -0,0 +1,4993 @@ + +const config = require('../setup/config'); +const Stripe = require('stripe'); + +const stripe = Stripe(config.stripe.secretKey); + +function convertExtraOptions(__extra) { + var extraOptions = null; + if (__extra && typeof __extra == "object" && Object.keys(__extra).length) { + extraOptions = {}; + for (var option in __extra) { + extraOptions[toCamelCase(option)] = __extra[option]; + } + } + return extraOptions; +} + +function toCamelCase(str) { + if (str == null) return str; + return String(str) + .toLowerCase() + .replace(/[\s_]+(\S)/g, function(a, b) { + return b.toUpperCase() + }); +} + + +// /v1/3d_secure - post +exports.createThreeDsecure = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.threeDSecure.create(options,__extra); +}; + +// /v1/3d_secure/{three_d_secure} - get +exports.retrieveThreeDsecure = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const three_d_secure = this.parseRequired(options.three_d_secure, 'string', 'stripe.retrieveThreeDsecure: three_d_secure is required.'); + delete options.three_d_secure; + return stripe.threeDSecure.retrieve(three_d_secure, options,__extra); +}; + +// /v1/account_links - post +exports.createAccountLink = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.accountLinks.create(options,__extra); +}; + +// /v1/accounts - get +exports.listAccounts = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.accounts.list(options,__extra); +}; + +// /v1/accounts - post +exports.createAccount = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.accounts.create(options,__extra); +}; + +// /v1/accounts/{account} - delete +exports.deleteAccount = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const account = this.parseRequired(options.account, 'string', 'stripe.deleteAccount: account is required.'); + delete options.account; + return stripe.accounts.del(account, options,__extra); +}; + +// /v1/accounts/{account} - get +exports.retrieveAccount = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const account = this.parseRequired(options.account, 'string', 'stripe.retrieveAccount: account is required.'); + delete options.account; + return stripe.accounts.retrieve(account, options,__extra); +}; + +// /v1/accounts/{account} - post +exports.updateAccount = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const account = this.parseRequired(options.account, 'string', 'stripe.updateAccount: account is required.'); + delete options.account; + return stripe.accounts.update(account, options,__extra); +}; + +// /v1/accounts/{account}/capabilities - get +exports.listAccountCapabilities = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const account = this.parseRequired(options.account, 'string', 'stripe.listAccountCapabilities: account is required.'); + delete options.account; + return stripe.accounts.listCapabilities(account, options,__extra); +}; + +// /v1/accounts/{account}/capabilities/{capability} - get +exports.retrieveAccountCapabilitie = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const account = this.parseRequired(options.account, 'string', 'stripe.retrieveAccountCapabilitie: account is required.'); + delete options.account; + const capability = this.parseRequired(options.capability, 'string', 'stripe.retrieveAccountCapabilitie: capability is required.'); + delete options.capability; + return stripe.accounts.retrieveCapabilitie(account, capability, options,__extra); +}; + +// /v1/accounts/{account}/capabilities/{capability} - post +exports.updateAccountCapabilitie = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const account = this.parseRequired(options.account, 'string', 'stripe.updateAccountCapabilitie: account is required.'); + delete options.account; + const capability = this.parseRequired(options.capability, 'string', 'stripe.updateAccountCapabilitie: capability is required.'); + delete options.capability; + return stripe.accounts.updateCapabilitie(account, capability, options,__extra); +}; + +// /v1/accounts/{account}/external_accounts - get +exports.listAccountExternalAccounts = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const account = this.parseRequired(options.account, 'string', 'stripe.listAccountExternalAccounts: account is required.'); + delete options.account; + return stripe.accounts.listExternalAccounts(account, options,__extra); +}; + +// /v1/accounts/{account}/external_accounts - post +exports.createAccountExternalAccount = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const account = this.parseRequired(options.account, 'string', 'stripe.createAccountExternalAccount: account is required.'); + delete options.account; + return stripe.accounts.createExternalAccount(account, options,__extra); +}; + +// /v1/accounts/{account}/external_accounts/{id} - delete +exports.deleteAccountExternalAccount = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const account = this.parseRequired(options.account, 'string', 'stripe.deleteAccountExternalAccount: account is required.'); + delete options.account; + const id = this.parseRequired(options.id, 'string', 'stripe.deleteAccountExternalAccount: id is required.'); + delete options.id; + return stripe.accounts.deleteExternalAccount(account, id, options,__extra); +}; + +// /v1/accounts/{account}/external_accounts/{id} - get +exports.retrieveAccountExternalAccount = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const account = this.parseRequired(options.account, 'string', 'stripe.retrieveAccountExternalAccount: account is required.'); + delete options.account; + const id = this.parseRequired(options.id, 'string', 'stripe.retrieveAccountExternalAccount: id is required.'); + delete options.id; + return stripe.accounts.retrieveExternalAccount(account, id, options,__extra); +}; + +// /v1/accounts/{account}/external_accounts/{id} - post +exports.updateAccountExternalAccount = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const account = this.parseRequired(options.account, 'string', 'stripe.updateAccountExternalAccount: account is required.'); + delete options.account; + const id = this.parseRequired(options.id, 'string', 'stripe.updateAccountExternalAccount: id is required.'); + delete options.id; + return stripe.accounts.updateExternalAccount(account, id, options,__extra); +}; + +// /v1/accounts/{account}/login_links - post +exports.createAccountLoginLink = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const account = this.parseRequired(options.account, 'string', 'stripe.createAccountLoginLink: account is required.'); + delete options.account; + return stripe.accounts.createLoginLink(account, options,__extra); +}; + +// /v1/accounts/{account}/persons - get +exports.listAccountPersons = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const account = this.parseRequired(options.account, 'string', 'stripe.listAccountPersons: account is required.'); + delete options.account; + return stripe.accounts.listPersons(account, options,__extra); +}; + +// /v1/accounts/{account}/persons - post +exports.createAccountPerson = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const account = this.parseRequired(options.account, 'string', 'stripe.createAccountPerson: account is required.'); + delete options.account; + return stripe.accounts.createPerson(account, options,__extra); +}; + +// /v1/accounts/{account}/persons/{person} - delete +exports.deleteAccountPerson = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const account = this.parseRequired(options.account, 'string', 'stripe.deleteAccountPerson: account is required.'); + delete options.account; + const person = this.parseRequired(options.person, 'string', 'stripe.deleteAccountPerson: person is required.'); + delete options.person; + return stripe.accounts.deletePerson(account, person, options,__extra); +}; + +// /v1/accounts/{account}/persons/{person} - get +exports.retrieveAccountPerson = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const account = this.parseRequired(options.account, 'string', 'stripe.retrieveAccountPerson: account is required.'); + delete options.account; + const person = this.parseRequired(options.person, 'string', 'stripe.retrieveAccountPerson: person is required.'); + delete options.person; + return stripe.accounts.retrievePerson(account, person, options,__extra); +}; + +// /v1/accounts/{account}/persons/{person} - post +exports.updateAccountPerson = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const account = this.parseRequired(options.account, 'string', 'stripe.updateAccountPerson: account is required.'); + delete options.account; + const person = this.parseRequired(options.person, 'string', 'stripe.updateAccountPerson: person is required.'); + delete options.person; + return stripe.accounts.updatePerson(account, person, options,__extra); +}; + +// /v1/accounts/{account}/reject - post +exports.rejectAccount = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const account = this.parseRequired(options.account, 'string', 'stripe.rejectAccount: account is required.'); + delete options.account; + return stripe.accounts.reject(account, options,__extra); +}; + +// /v1/accounts/{account}/x-stripeParametersOverride_bank_account/{id} - post +exports.updateAccountXStripeParametersOverrideBankAccount = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const account = this.parseRequired(options.account, 'string', 'stripe.updateAccountXStripeParametersOverrideBankAccount: account is required.'); + delete options.account; + const id = this.parseRequired(options.id, 'string', 'stripe.updateAccountXStripeParametersOverrideBankAccount: id is required.'); + delete options.id; + return stripe.accounts.updateXStripeParametersOverrideBankAccount(account, id, options,__extra); +}; + +// /v1/accounts/{account}/x-stripeParametersOverride_card/{id} - post +exports.updateAccountXStripeParametersOverrideCard = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const account = this.parseRequired(options.account, 'string', 'stripe.updateAccountXStripeParametersOverrideCard: account is required.'); + delete options.account; + const id = this.parseRequired(options.id, 'string', 'stripe.updateAccountXStripeParametersOverrideCard: id is required.'); + delete options.id; + return stripe.accounts.updateXStripeParametersOverrideCard(account, id, options,__extra); +}; + +// /v1/apple_pay/domains - get +exports.listApplePayDomains = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.applePay.domains.list(options,__extra); +}; + +// /v1/apple_pay/domains - post +exports.createApplePayDomain = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.applePay.domains.create(options,__extra); +}; + +// /v1/apple_pay/domains/{domain} - delete +exports.deleteApplePayDomain = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const domain = this.parseRequired(options.domain, 'string', 'stripe.deleteApplePayDomain: domain is required.'); + delete options.domain; + return stripe.applePay.domains.del(domain, options,__extra); +}; + +// /v1/apple_pay/domains/{domain} - get +exports.retrieveApplePayDomain = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const domain = this.parseRequired(options.domain, 'string', 'stripe.retrieveApplePayDomain: domain is required.'); + delete options.domain; + return stripe.applePay.domains.retrieve(domain, options,__extra); +}; + +// /v1/application_fees - get +exports.listApplicationFees = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.applicationFees.list(options,__extra); +}; + +// /v1/application_fees/{fee}/refunds/{id} - get +exports.retrieveApplicationFeeRefund = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const fee = this.parseRequired(options.fee, 'string', 'stripe.retrieveApplicationFeeRefund: fee is required.'); + delete options.fee; + const id = this.parseRequired(options.id, 'string', 'stripe.retrieveApplicationFeeRefund: id is required.'); + delete options.id; + return stripe.applicationFees.retrieveRefund(fee, id, options,__extra); +}; + +// /v1/application_fees/{fee}/refunds/{id} - post +exports.updateApplicationFeeRefund = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const fee = this.parseRequired(options.fee, 'string', 'stripe.updateApplicationFeeRefund: fee is required.'); + delete options.fee; + const id = this.parseRequired(options.id, 'string', 'stripe.updateApplicationFeeRefund: id is required.'); + delete options.id; + return stripe.applicationFees.updateRefund(fee, id, options,__extra); +}; + +// /v1/application_fees/{id} - get +exports.retrieveApplicationFee = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const id = this.parseRequired(options.id, 'string', 'stripe.retrieveApplicationFee: id is required.'); + delete options.id; + return stripe.applicationFees.retrieve(id, options,__extra); +}; + +// /v1/application_fees/{id}/refunds - get +exports.listApplicationFeeRefunds = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const id = this.parseRequired(options.id, 'string', 'stripe.listApplicationFeeRefunds: id is required.'); + delete options.id; + return stripe.applicationFees.listRefunds(id, options,__extra); +}; + +// /v1/application_fees/{id}/refunds - post +exports.createApplicationFeeRefund = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const id = this.parseRequired(options.id, 'string', 'stripe.createApplicationFeeRefund: id is required.'); + delete options.id; + return stripe.applicationFees.createRefund(id, options,__extra); +}; + +// /v1/balance - get +exports.retrieveBalance = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.balance.retrieve(options,__extra); +}; + +// /v1/balance_transactions - get +exports.listBalanceTransactions = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.balanceTransactions.list(options,__extra); +}; + +// /v1/balance_transactions/{id} - get +exports.retrieveBalanceTransaction = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const id = this.parseRequired(options.id, 'string', 'stripe.retrieveBalanceTransaction: id is required.'); + delete options.id; + return stripe.balanceTransactions.retrieve(id, options,__extra); +}; + +// /v1/billing_portal/configurations - get +exports.listBillingPortalConfigurations = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.billingPortal.configurations.list(options,__extra); +}; + +// /v1/billing_portal/configurations - post +exports.createBillingPortalConfiguration = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.billingPortal.configurations.create(options,__extra); +}; + +// /v1/billing_portal/configurations/{configuration} - get +exports.retrieveBillingPortalConfiguration = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const configuration = this.parseRequired(options.configuration, 'string', 'stripe.retrieveBillingPortalConfiguration: configuration is required.'); + delete options.configuration; + return stripe.billingPortal.configurations.retrieve(configuration, options,__extra); +}; + +// /v1/billing_portal/configurations/{configuration} - post +exports.updateBillingPortalConfiguration = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const configuration = this.parseRequired(options.configuration, 'string', 'stripe.updateBillingPortalConfiguration: configuration is required.'); + delete options.configuration; + return stripe.billingPortal.configurations.update(configuration, options,__extra); +}; + +// /v1/billing_portal/sessions - post +exports.createBillingPortalSession = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.billingPortal.sessions.create(options,__extra); +}; + +// /v1/bitcoin/receivers - get +exports.listBitcoinReceivers = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.bitcoin.receivers.list(options,__extra); +}; + +// /v1/bitcoin/receivers/{id} - get +exports.retrieveBitcoinReceiver = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const id = this.parseRequired(options.id, 'string', 'stripe.retrieveBitcoinReceiver: id is required.'); + delete options.id; + return stripe.bitcoin.receivers.retrieve(id, options,__extra); +}; + +// /v1/bitcoin/receivers/{receiver}/transactions - get +exports.listBitcoinReceiverTransactions = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const receiver = this.parseRequired(options.receiver, 'string', 'stripe.listBitcoinReceiverTransactions: receiver is required.'); + delete options.receiver; + return stripe.bitcoin.receivers.listTransactions(receiver, options,__extra); +}; + +// /v1/charges - get +exports.listCharges = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.charges.list(options,__extra); +}; + +// /v1/charges - post +exports.createCharge = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.charges.create(options,__extra); +}; + +// /v1/charges/search - get +exports.searchCharges = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const query = this.parseRequired(options.query, 'string', 'stripe.searchCharges: query is required.'); + return stripe.charges.search(options,__extra); +}; + +// /v1/charges/{charge} - get +exports.retrieveCharge = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const charge = this.parseRequired(options.charge, 'string', 'stripe.retrieveCharge: charge is required.'); + delete options.charge; + return stripe.charges.retrieve(charge, options,__extra); +}; + +// /v1/charges/{charge} - post +exports.updateCharge = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const charge = this.parseRequired(options.charge, 'string', 'stripe.updateCharge: charge is required.'); + delete options.charge; + return stripe.charges.update(charge, options,__extra); +}; + +// /v1/charges/{charge}/capture - post +exports.captureCharge = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const charge = this.parseRequired(options.charge, 'string', 'stripe.captureCharge: charge is required.'); + delete options.charge; + return stripe.charges.capture(charge, options,__extra); +}; + +// /v1/charges/{charge}/refunds - get +exports.listChargeRefunds = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const charge = this.parseRequired(options.charge, 'string', 'stripe.listChargeRefunds: charge is required.'); + delete options.charge; + return stripe.charges.listRefunds(charge, options,__extra); +}; + +// /v1/charges/{charge}/refunds/{refund} - get +exports.retrieveChargeRefund = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const charge = this.parseRequired(options.charge, 'string', 'stripe.retrieveChargeRefund: charge is required.'); + delete options.charge; + const refund = this.parseRequired(options.refund, 'string', 'stripe.retrieveChargeRefund: refund is required.'); + delete options.refund; + return stripe.charges.retrieveRefund(charge, refund, options,__extra); +}; + +// /v1/checkout/sessions - get +exports.listCheckoutSessions = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.checkout.sessions.list(options,__extra); +}; + +// /v1/checkout/sessions - post +exports.createCheckoutSession = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + if (options && options.lineItemsType) { + if (options.lineItemsType == 'customList' || options.lineItemsType == 'customRef') { + if (options.line_items) { + if (!Array.isArray(options.line_items)) { + if (typeof options.line_items == "object") { + options.line_items = [options.line_items]; + } else { + throw new Error('createCheckoutSession: line_items is not an array or object for references'); + } + } else if (!options.line_items || options.line_items.length == 0) { + throw new Error('createCheckoutSession: line_items is empty!'); + } + } else { + throw new Error('createCheckoutSession: line_items is not set!'); + } + options.line_items = options.line_items.map(item => { + return { + price_data: { + currency: item.currency ? item.currency : 'USD', + product_data: { + name: item.title, + }, + unit_amount_decimal: item.amount, + }, + quantity: item.quantity ? item.quantity : 1, + } + }) + } + delete options.lineItemsType; + } else { + if (options.line_items) { + if (Array.isArray(options.line_items)) { + if (options.line_items.length == 0) { + throw new Error('createCheckoutSession: line_items is empty!'); + } + options.line_items = options.line_items.map(item => { + return { + price: item.price, + quantity: item.quantity ? item.quantity : 1 + } + }) + } else if (typeof options.line_items == "object") { + options.line_items = [options.line_items] + } else if (typeof options.line_items == "string") { + options.line_items = [{price: options.line_items, quantity: 1}] + } + } + } + + return stripe.checkout.sessions.create(options,__extra); +}; + +// /v1/checkout/sessions/{session} - get +exports.retrieveCheckoutSession = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const session = this.parseRequired(options.session, 'string', 'stripe.retrieveCheckoutSession: session is required.'); + delete options.session; + return stripe.checkout.sessions.retrieve(session, options,__extra); +}; + +// /v1/checkout/sessions/{session}/expire - post +exports.expireCheckoutSession = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const session = this.parseRequired(options.session, 'string', 'stripe.expireCheckoutSession: session is required.'); + delete options.session; + return stripe.checkout.sessions.expire(session, options,__extra); +}; + +// /v1/checkout/sessions/{session}/line_items - get +exports.listCheckoutSessionLineItems = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const session = this.parseRequired(options.session, 'string', 'stripe.listCheckoutSessionLineItems: session is required.'); + delete options.session; + return stripe.checkout.sessions.listLineItems(session, options,__extra); +}; + +// /v1/country_specs - get +exports.listCountrySpecs = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.countrySpecs.list(options,__extra); +}; + +// /v1/country_specs/{country} - get +exports.retrieveCountrySpec = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const country = this.parseRequired(options.country, 'string', 'stripe.retrieveCountrySpec: country is required.'); + delete options.country; + return stripe.countrySpecs.retrieve(country, options,__extra); +}; + +// /v1/coupons - get +exports.listCoupons = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.coupons.list(options,__extra); +}; + +// /v1/coupons - post +exports.createCoupon = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.coupons.create(options,__extra); +}; + +// /v1/coupons/{coupon} - delete +exports.deleteCoupon = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const coupon = this.parseRequired(options.coupon, 'string', 'stripe.deleteCoupon: coupon is required.'); + delete options.coupon; + return stripe.coupons.del(coupon, options,__extra); +}; + +// /v1/coupons/{coupon} - get +exports.retrieveCoupon = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const coupon = this.parseRequired(options.coupon, 'string', 'stripe.retrieveCoupon: coupon is required.'); + delete options.coupon; + return stripe.coupons.retrieve(coupon, options,__extra); +}; + +// /v1/coupons/{coupon} - post +exports.updateCoupon = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const coupon = this.parseRequired(options.coupon, 'string', 'stripe.updateCoupon: coupon is required.'); + delete options.coupon; + return stripe.coupons.update(coupon, options,__extra); +}; + +// /v1/credit_notes - get +exports.listCreditNotes = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.creditNotes.list(options,__extra); +}; + +// /v1/credit_notes - post +exports.createCreditNote = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.creditNotes.create(options,__extra); +}; + +// /v1/credit_notes/preview - get +exports.retrieveCreditNotesPreview = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const invoice = this.parseRequired(options.invoice, 'string', 'stripe.retrieveCreditNotesPreview: invoice is required.'); + return stripe.creditNotes.preview(options,__extra); +}; + +// /v1/credit_notes/preview/lines - get +exports.listCreditNotesPreviewLines = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const invoice = this.parseRequired(options.invoice, 'string', 'stripe.listCreditNotesPreviewLines: invoice is required.'); + return stripe.creditNotes.listPreviewLineItems(options,__extra); +}; + +// /v1/credit_notes/{credit_note}/lines - get +exports.listCreditNoteLines = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const credit_note = this.parseRequired(options.credit_note, 'string', 'stripe.listCreditNoteLines: credit_note is required.'); + delete options.credit_note; + return stripe.creditNotes.listLines(credit_note, options,__extra); +}; + +// /v1/credit_notes/{id} - get +exports.retrieveCreditNote = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const id = this.parseRequired(options.id, 'string', 'stripe.retrieveCreditNote: id is required.'); + delete options.id; + return stripe.creditNotes.retrieve(id, options,__extra); +}; + +// /v1/credit_notes/{id} - post +exports.updateCreditNote = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const id = this.parseRequired(options.id, 'string', 'stripe.updateCreditNote: id is required.'); + delete options.id; + return stripe.creditNotes.update(id, options,__extra); +}; + +// /v1/credit_notes/{id}/void - post +exports.voidCreditNote = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const id = this.parseRequired(options.id, 'string', 'stripe.voidCreditNote: id is required.'); + delete options.id; + return stripe.creditNotes.voidCreditNote(id, options,__extra); +}; + +// /v1/customers - get +exports.listCustomers = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.customers.list(options,__extra); +}; + +// /v1/customers - post +exports.createCustomer = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.customers.create(options,__extra); +}; + +// /v1/customers/search - get +exports.searchCustomers = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const query = this.parseRequired(options.query, 'string', 'stripe.searchCustomers: query is required.'); + return stripe.customers.search(options,__extra); +}; + +// /v1/customers/{customer} - delete +exports.deleteCustomer = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const customer = this.parseRequired(options.customer, 'string', 'stripe.deleteCustomer: customer is required.'); + delete options.customer; + return stripe.customers.del(customer, options,__extra); +}; + +// /v1/customers/{customer} - get +exports.retrieveCustomer = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const customer = this.parseRequired(options.customer, 'string', 'stripe.retrieveCustomer: customer is required.'); + delete options.customer; + return stripe.customers.retrieve(customer, options,__extra); +}; + +// /v1/customers/{customer} - post +exports.updateCustomer = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const customer = this.parseRequired(options.customer, 'string', 'stripe.updateCustomer: customer is required.'); + delete options.customer; + return stripe.customers.update(customer, options,__extra); +}; + +// /v1/customers/{customer}/balance_transactions - get +exports.listCustomerBalanceTransactions = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const customer = this.parseRequired(options.customer, 'string', 'stripe.listCustomerBalanceTransactions: customer is required.'); + delete options.customer; + return stripe.customers.listBalanceTransactions(customer, options,__extra); +}; + +// /v1/customers/{customer}/balance_transactions - post +exports.createCustomerBalanceTransaction = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const customer = this.parseRequired(options.customer, 'string', 'stripe.createCustomerBalanceTransaction: customer is required.'); + delete options.customer; + return stripe.customers.createBalanceTransaction(customer, options,__extra); +}; + +// /v1/customers/{customer}/balance_transactions/{transaction} - get +exports.retrieveCustomerBalanceTransaction = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const customer = this.parseRequired(options.customer, 'string', 'stripe.retrieveCustomerBalanceTransaction: customer is required.'); + delete options.customer; + const transaction = this.parseRequired(options.transaction, 'string', 'stripe.retrieveCustomerBalanceTransaction: transaction is required.'); + delete options.transaction; + return stripe.customers.retrieveBalanceTransaction(customer, transaction, options,__extra); +}; + +// /v1/customers/{customer}/balance_transactions/{transaction} - post +exports.updateCustomerBalanceTransaction = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const customer = this.parseRequired(options.customer, 'string', 'stripe.updateCustomerBalanceTransaction: customer is required.'); + delete options.customer; + const transaction = this.parseRequired(options.transaction, 'string', 'stripe.updateCustomerBalanceTransaction: transaction is required.'); + delete options.transaction; + return stripe.customers.updateBalanceTransaction(customer, transaction, options,__extra); +}; + +// /v1/customers/{customer}/cash_balance - get +exports.retrieveCustomerCashBalance = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const customer = this.parseRequired(options.customer, 'string', 'stripe.retrieveCustomerCashBalance: customer is required.'); + delete options.customer; + return stripe.customers.retrieveCashBalance(customer, options,__extra); +}; + +// /v1/customers/{customer}/cash_balance - post +exports.cashBalanceCustomer = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const customer = this.parseRequired(options.customer, 'string', 'stripe.cashBalanceCustomer: customer is required.'); + delete options.customer; + return stripe.customers.cashBalance(customer, options,__extra); +}; + +// /v1/customers/{customer}/discount - delete +exports.deleteCustomerDiscount = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const customer = this.parseRequired(options.customer, 'string', 'stripe.deleteCustomerDiscount: customer is required.'); + delete options.customer; + return stripe.customers.deleteDiscount(customer, options,__extra); +}; + +// /v1/customers/{customer}/funding_instructions - post +exports.createCustomerFundingInstruction = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const customer = this.parseRequired(options.customer, 'string', 'stripe.createCustomerFundingInstruction: customer is required.'); + delete options.customer; + return stripe.customers.createFundingInstruction(customer, options,__extra); +}; + +// /v1/customers/{customer}/payment_methods - get +exports.listCustomerPaymentMethods = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const customer = this.parseRequired(options.customer, 'string', 'stripe.listCustomerPaymentMethods: customer is required.'); + delete options.customer; + const type = this.parseRequired(options.type, 'string', 'stripe.listCustomerPaymentMethods: type is required.'); + return stripe.customers.listPaymentMethods(customer, options,__extra); +}; + +// /v1/customers/{customer}/sources - get +exports.listCustomerSources = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const customer = this.parseRequired(options.customer, 'string', 'stripe.listCustomerSources: customer is required.'); + delete options.customer; + return stripe.customers.listSources(customer, options,__extra); +}; + +// /v1/customers/{customer}/sources - post +exports.createCustomerSource = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const customer = this.parseRequired(options.customer, 'string', 'stripe.createCustomerSource: customer is required.'); + delete options.customer; + return stripe.customers.createSource(customer, options,__extra); +}; + +// /v1/customers/{customer}/sources/{id} - delete +exports.deleteCustomerSource = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const customer = this.parseRequired(options.customer, 'string', 'stripe.deleteCustomerSource: customer is required.'); + delete options.customer; + const id = this.parseRequired(options.id, 'string', 'stripe.deleteCustomerSource: id is required.'); + delete options.id; + return stripe.customers.deleteSource(customer, id, options,__extra); +}; + +// /v1/customers/{customer}/sources/{id} - get +exports.retrieveCustomerSource = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const customer = this.parseRequired(options.customer, 'string', 'stripe.retrieveCustomerSource: customer is required.'); + delete options.customer; + const id = this.parseRequired(options.id, 'string', 'stripe.retrieveCustomerSource: id is required.'); + delete options.id; + return stripe.customers.retrieveSource(customer, id, options,__extra); +}; + +// /v1/customers/{customer}/sources/{id} - post +exports.updateCustomerSource = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const customer = this.parseRequired(options.customer, 'string', 'stripe.updateCustomerSource: customer is required.'); + delete options.customer; + const id = this.parseRequired(options.id, 'string', 'stripe.updateCustomerSource: id is required.'); + delete options.id; + return stripe.customers.updateSource(customer, id, options,__extra); +}; + +// /v1/customers/{customer}/tax_ids - get +exports.listCustomerTaxIds = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const customer = this.parseRequired(options.customer, 'string', 'stripe.listCustomerTaxIds: customer is required.'); + delete options.customer; + return stripe.customers.listTaxIds(customer, options,__extra); +}; + +// /v1/customers/{customer}/tax_ids - post +exports.createCustomerTaxId = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const customer = this.parseRequired(options.customer, 'string', 'stripe.createCustomerTaxId: customer is required.'); + delete options.customer; + return stripe.customers.createTaxId(customer, options,__extra); +}; + +// /v1/customers/{customer}/tax_ids/{id} - delete +exports.deleteCustomerTaxId = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const customer = this.parseRequired(options.customer, 'string', 'stripe.deleteCustomerTaxId: customer is required.'); + delete options.customer; + const id = this.parseRequired(options.id, 'string', 'stripe.deleteCustomerTaxId: id is required.'); + delete options.id; + return stripe.customers.deleteTaxId(customer, id, options,__extra); +}; + +// /v1/customers/{customer}/tax_ids/{id} - get +exports.retrieveCustomerTaxId = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const customer = this.parseRequired(options.customer, 'string', 'stripe.retrieveCustomerTaxId: customer is required.'); + delete options.customer; + const id = this.parseRequired(options.id, 'string', 'stripe.retrieveCustomerTaxId: id is required.'); + delete options.id; + return stripe.customers.retrieveTaxId(customer, id, options,__extra); +}; + +// /v1/customers/{customer}/x-stripeParametersOverride_bank_accounts/{id} - post +exports.updateCustomerXStripeParametersOverrideBankAccount = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const customer = this.parseRequired(options.customer, 'string', 'stripe.updateCustomerXStripeParametersOverrideBankAccount: customer is required.'); + delete options.customer; + const id = this.parseRequired(options.id, 'string', 'stripe.updateCustomerXStripeParametersOverrideBankAccount: id is required.'); + delete options.id; + return stripe.customers.updateXStripeParametersOverrideBankAccount(customer, id, options,__extra); +}; + +// /v1/customers/{customer}/x-stripeParametersOverride_cards/{id} - post +exports.updateCustomerXStripeParametersOverrideCard = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const customer = this.parseRequired(options.customer, 'string', 'stripe.updateCustomerXStripeParametersOverrideCard: customer is required.'); + delete options.customer; + const id = this.parseRequired(options.id, 'string', 'stripe.updateCustomerXStripeParametersOverrideCard: id is required.'); + delete options.id; + return stripe.customers.updateXStripeParametersOverrideCard(customer, id, options,__extra); +}; + +// /v1/disputes - get +exports.listDisputes = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.disputes.list(options,__extra); +}; + +// /v1/disputes/{dispute} - get +exports.retrieveDispute = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const dispute = this.parseRequired(options.dispute, 'string', 'stripe.retrieveDispute: dispute is required.'); + delete options.dispute; + return stripe.disputes.retrieve(dispute, options,__extra); +}; + +// /v1/disputes/{dispute} - post +exports.updateDispute = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const dispute = this.parseRequired(options.dispute, 'string', 'stripe.updateDispute: dispute is required.'); + delete options.dispute; + return stripe.disputes.update(dispute, options,__extra); +}; + +// /v1/disputes/{dispute}/close - post +exports.closeDispute = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const dispute = this.parseRequired(options.dispute, 'string', 'stripe.closeDispute: dispute is required.'); + delete options.dispute; + return stripe.disputes.close(dispute, options,__extra); +}; + +// /v1/ephemeral_keys - post +exports.createEphemeralKey = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.ephemeralKeys.create(options,__extra); +}; + +// /v1/ephemeral_keys/{key} - delete +exports.deleteEphemeralKey = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const key = this.parseRequired(options.key, 'string', 'stripe.deleteEphemeralKey: key is required.'); + delete options.key; + return stripe.ephemeralKeys.del(key, options,__extra); +}; + +// /v1/events - get +exports.listEvents = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.events.list(options,__extra); +}; + +// /v1/events/{id} - get +exports.retrieveEvent = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const id = this.parseRequired(options.id, 'string', 'stripe.retrieveEvent: id is required.'); + delete options.id; + return stripe.events.retrieve(id, options,__extra); +}; + +// /v1/exchange_rates - get +exports.listExchangeRates = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.exchangeRates.list(options,__extra); +}; + +// /v1/exchange_rates/{rate_id} - get +exports.retrieveExchangeRate = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const rate_id = this.parseRequired(options.rate_id, 'string', 'stripe.retrieveExchangeRate: rate_id is required.'); + delete options.rate_id; + return stripe.exchangeRates.retrieve(rate_id, options,__extra); +}; + +// /v1/file_links - get +exports.listFileLinks = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.fileLinks.list(options,__extra); +}; + +// /v1/file_links - post +exports.createFileLink = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.fileLinks.create(options,__extra); +}; + +// /v1/file_links/{link} - get +exports.retrieveFileLink = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const link = this.parseRequired(options.link, 'string', 'stripe.retrieveFileLink: link is required.'); + delete options.link; + return stripe.fileLinks.retrieve(link, options,__extra); +}; + +// /v1/file_links/{link} - post +exports.updateFileLink = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const link = this.parseRequired(options.link, 'string', 'stripe.updateFileLink: link is required.'); + delete options.link; + return stripe.fileLinks.update(link, options,__extra); +}; + +// /v1/files - get +exports.listFiles = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.files.list(options,__extra); +}; + +// /v1/files - post +exports.createFile = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.files.create(options,__extra); +}; + +// /v1/files/{file} - get +exports.retrieveFile = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const file = this.parseRequired(options.file, 'string', 'stripe.retrieveFile: file is required.'); + delete options.file; + return stripe.files.retrieve(file, options,__extra); +}; + +// /v1/financial_connections/accounts/{account} - get +exports.retrieveFinancialConnectionsAccount = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const account = this.parseRequired(options.account, 'string', 'stripe.retrieveFinancialConnectionsAccount: account is required.'); + delete options.account; + return stripe.financialConnections.accounts.retrieve(account, options,__extra); +}; + +// /v1/financial_connections/accounts/{account}/disconnect - post +exports.disconnectFinancialConnectionsAccount = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const account = this.parseRequired(options.account, 'string', 'stripe.disconnectFinancialConnectionsAccount: account is required.'); + delete options.account; + return stripe.financialConnections.accounts.disconnect(account, options,__extra); +}; + +// /v1/financial_connections/accounts/{account}/refresh - post +exports.refreshFinancialConnectionsAccount = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const account = this.parseRequired(options.account, 'string', 'stripe.refreshFinancialConnectionsAccount: account is required.'); + delete options.account; + return stripe.financialConnections.accounts.refresh(account, options,__extra); +}; + +// /v1/financial_connections/sessions - post +exports.createFinancialConnectionsSession = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.financialConnections.sessions.create(options,__extra); +}; + +// /v1/financial_connections/sessions/{session} - get +exports.retrieveFinancialConnectionsSession = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const session = this.parseRequired(options.session, 'string', 'stripe.retrieveFinancialConnectionsSession: session is required.'); + delete options.session; + return stripe.financialConnections.sessions.retrieve(session, options,__extra); +}; + +// /v1/identity/verification_reports - get +exports.listIdentityVerificationReports = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.identity.verificationReports.list(options,__extra); +}; + +// /v1/identity/verification_reports/{report} - get +exports.retrieveIdentityVerificationReport = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const report = this.parseRequired(options.report, 'string', 'stripe.retrieveIdentityVerificationReport: report is required.'); + delete options.report; + return stripe.identity.verificationReports.retrieve(report, options,__extra); +}; + +// /v1/identity/verification_sessions - get +exports.listIdentityVerificationSessions = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.identity.verificationSessions.list(options,__extra); +}; + +// /v1/identity/verification_sessions - post +exports.createIdentityVerificationSession = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.identity.verificationSessions.create(options,__extra); +}; + +// /v1/identity/verification_sessions/{session} - get +exports.retrieveIdentityVerificationSession = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const session = this.parseRequired(options.session, 'string', 'stripe.retrieveIdentityVerificationSession: session is required.'); + delete options.session; + return stripe.identity.verificationSessions.retrieve(session, options,__extra); +}; + +// /v1/identity/verification_sessions/{session} - post +exports.updateIdentityVerificationSession = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const session = this.parseRequired(options.session, 'string', 'stripe.updateIdentityVerificationSession: session is required.'); + delete options.session; + return stripe.identity.verificationSessions.update(session, options,__extra); +}; + +// /v1/identity/verification_sessions/{session}/cancel - post +exports.cancelIdentityVerificationSession = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const session = this.parseRequired(options.session, 'string', 'stripe.cancelIdentityVerificationSession: session is required.'); + delete options.session; + return stripe.identity.verificationSessions.cancel(session, options,__extra); +}; + +// /v1/identity/verification_sessions/{session}/redact - post +exports.redactIdentityVerificationSession = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const session = this.parseRequired(options.session, 'string', 'stripe.redactIdentityVerificationSession: session is required.'); + delete options.session; + return stripe.identity.verificationSessions.redact(session, options,__extra); +}; + +// /v1/invoiceitems - get +exports.listInvoiceitems = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.invoiceitems.list(options,__extra); +}; + +// /v1/invoiceitems - post +exports.createInvoiceitem = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.invoiceitems.create(options,__extra); +}; + +// /v1/invoiceitems/{invoiceitem} - delete +exports.deleteInvoiceitem = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const invoiceitem = this.parseRequired(options.invoiceitem, 'string', 'stripe.deleteInvoiceitem: invoiceitem is required.'); + delete options.invoiceitem; + return stripe.invoiceitems.del(invoiceitem, options,__extra); +}; + +// /v1/invoiceitems/{invoiceitem} - get +exports.retrieveInvoiceitem = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const invoiceitem = this.parseRequired(options.invoiceitem, 'string', 'stripe.retrieveInvoiceitem: invoiceitem is required.'); + delete options.invoiceitem; + return stripe.invoiceitems.retrieve(invoiceitem, options,__extra); +}; + +// /v1/invoiceitems/{invoiceitem} - post +exports.updateInvoiceitem = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const invoiceitem = this.parseRequired(options.invoiceitem, 'string', 'stripe.updateInvoiceitem: invoiceitem is required.'); + delete options.invoiceitem; + return stripe.invoiceitems.update(invoiceitem, options,__extra); +}; + +// /v1/invoices - get +exports.listInvoices = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.invoices.list(options,__extra); +}; + +// /v1/invoices - post +exports.createInvoice = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.invoices.create(options,__extra); +}; + +// /v1/invoices/search - get +exports.searchInvoices = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const query = this.parseRequired(options.query, 'string', 'stripe.searchInvoices: query is required.'); + return stripe.invoices.search(options,__extra); +}; + +// /v1/invoices/upcoming - get +exports.retrieveInvoicesUpcoming = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.invoices.retrieveUpcoming(options,__extra); +}; + +// /v1/invoices/upcoming/lines - get +exports.listInvoicesUpcomingLines = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.invoices.listUpcomingLineItems(options,__extra); +}; + +// /v1/invoices/{invoice} - delete +exports.deleteInvoice = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const invoice = this.parseRequired(options.invoice, 'string', 'stripe.deleteInvoice: invoice is required.'); + delete options.invoice; + return stripe.invoices.del(invoice, options,__extra); +}; + +// /v1/invoices/{invoice} - get +exports.retrieveInvoice = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const invoice = this.parseRequired(options.invoice, 'string', 'stripe.retrieveInvoice: invoice is required.'); + delete options.invoice; + return stripe.invoices.retrieve(invoice, options,__extra); +}; + +// /v1/invoices/{invoice} - post +exports.updateInvoice = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const invoice = this.parseRequired(options.invoice, 'string', 'stripe.updateInvoice: invoice is required.'); + delete options.invoice; + return stripe.invoices.update(invoice, options,__extra); +}; + +// /v1/invoices/{invoice}/finalize - post +exports.finalizeInvoice = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const invoice = this.parseRequired(options.invoice, 'string', 'stripe.finalizeInvoice: invoice is required.'); + delete options.invoice; + return stripe.invoices.finalizeInvoice(invoice, options,__extra); +}; + +// /v1/invoices/{invoice}/lines - get +exports.listInvoiceLines = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const invoice = this.parseRequired(options.invoice, 'string', 'stripe.listInvoiceLines: invoice is required.'); + delete options.invoice; + return stripe.invoices.listLines(invoice, options,__extra); +}; + +// /v1/invoices/{invoice}/mark_uncollectible - post +exports.markUncollectibleInvoice = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const invoice = this.parseRequired(options.invoice, 'string', 'stripe.markUncollectibleInvoice: invoice is required.'); + delete options.invoice; + return stripe.invoices.markUncollectible(invoice, options,__extra); +}; + +// /v1/invoices/{invoice}/pay - post +exports.payInvoice = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const invoice = this.parseRequired(options.invoice, 'string', 'stripe.payInvoice: invoice is required.'); + delete options.invoice; + return stripe.invoices.pay(invoice, options,__extra); +}; + +// /v1/invoices/{invoice}/send - post +exports.sendInvoice = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const invoice = this.parseRequired(options.invoice, 'string', 'stripe.sendInvoice: invoice is required.'); + delete options.invoice; + return stripe.invoices.sendInvoice(invoice, options,__extra); +}; + +// /v1/invoices/{invoice}/void - post +exports.voidInvoice = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const invoice = this.parseRequired(options.invoice, 'string', 'stripe.voidInvoice: invoice is required.'); + delete options.invoice; + return stripe.invoices.voidInvoice(invoice, options,__extra); +}; + +// /v1/issuer_fraud_records - get +exports.listIssuerFraudRecords = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.issuerFraudRecords.list(options,__extra); +}; + +// /v1/issuer_fraud_records/{issuer_fraud_record} - get +exports.retrieveIssuerFraudRecord = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const issuer_fraud_record = this.parseRequired(options.issuer_fraud_record, 'string', 'stripe.retrieveIssuerFraudRecord: issuer_fraud_record is required.'); + delete options.issuer_fraud_record; + return stripe.issuerFraudRecords.retrieve(issuer_fraud_record, options,__extra); +}; + +// /v1/issuing/authorizations - get +exports.listIssuingAuthorizations = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.issuing.authorizations.list(options,__extra); +}; + +// /v1/issuing/authorizations/{authorization} - get +exports.retrieveIssuingAuthorization = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const authorization = this.parseRequired(options.authorization, 'string', 'stripe.retrieveIssuingAuthorization: authorization is required.'); + delete options.authorization; + return stripe.issuing.authorizations.retrieve(authorization, options,__extra); +}; + +// /v1/issuing/authorizations/{authorization} - post +exports.updateIssuingAuthorization = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const authorization = this.parseRequired(options.authorization, 'string', 'stripe.updateIssuingAuthorization: authorization is required.'); + delete options.authorization; + return stripe.issuing.authorizations.update(authorization, options,__extra); +}; + +// /v1/issuing/authorizations/{authorization}/approve - post +exports.approveIssuingAuthorization = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const authorization = this.parseRequired(options.authorization, 'string', 'stripe.approveIssuingAuthorization: authorization is required.'); + delete options.authorization; + return stripe.issuing.authorizations.approve(authorization, options,__extra); +}; + +// /v1/issuing/authorizations/{authorization}/decline - post +exports.declineIssuingAuthorization = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const authorization = this.parseRequired(options.authorization, 'string', 'stripe.declineIssuingAuthorization: authorization is required.'); + delete options.authorization; + return stripe.issuing.authorizations.decline(authorization, options,__extra); +}; + +// /v1/issuing/cardholders - get +exports.listIssuingCardholders = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.issuing.cardholders.list(options,__extra); +}; + +// /v1/issuing/cardholders - post +exports.createIssuingCardholder = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.issuing.cardholders.create(options,__extra); +}; + +// /v1/issuing/cardholders/{cardholder} - get +exports.retrieveIssuingCardholder = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const cardholder = this.parseRequired(options.cardholder, 'string', 'stripe.retrieveIssuingCardholder: cardholder is required.'); + delete options.cardholder; + return stripe.issuing.cardholders.retrieve(cardholder, options,__extra); +}; + +// /v1/issuing/cardholders/{cardholder} - post +exports.updateIssuingCardholder = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const cardholder = this.parseRequired(options.cardholder, 'string', 'stripe.updateIssuingCardholder: cardholder is required.'); + delete options.cardholder; + return stripe.issuing.cardholders.update(cardholder, options,__extra); +}; + +// /v1/issuing/cards - get +exports.listIssuingCards = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.issuing.cards.list(options,__extra); +}; + +// /v1/issuing/cards - post +exports.createIssuingCard = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.issuing.cards.create(options,__extra); +}; + +// /v1/issuing/cards/{card} - get +exports.retrieveIssuingCard = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const card = this.parseRequired(options.card, 'string', 'stripe.retrieveIssuingCard: card is required.'); + delete options.card; + return stripe.issuing.cards.retrieve(card, options,__extra); +}; + +// /v1/issuing/cards/{card} - post +exports.updateIssuingCard = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const card = this.parseRequired(options.card, 'string', 'stripe.updateIssuingCard: card is required.'); + delete options.card; + return stripe.issuing.cards.update(card, options,__extra); +}; + +// /v1/issuing/disputes - get +exports.listIssuingDisputes = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.issuing.disputes.list(options,__extra); +}; + +// /v1/issuing/disputes - post +exports.createIssuingDispute = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.issuing.disputes.create(options,__extra); +}; + +// /v1/issuing/disputes/{dispute} - get +exports.retrieveIssuingDispute = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const dispute = this.parseRequired(options.dispute, 'string', 'stripe.retrieveIssuingDispute: dispute is required.'); + delete options.dispute; + return stripe.issuing.disputes.retrieve(dispute, options,__extra); +}; + +// /v1/issuing/disputes/{dispute} - post +exports.updateIssuingDispute = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const dispute = this.parseRequired(options.dispute, 'string', 'stripe.updateIssuingDispute: dispute is required.'); + delete options.dispute; + return stripe.issuing.disputes.update(dispute, options,__extra); +}; + +// /v1/issuing/disputes/{dispute}/submit - post +exports.submitIssuingDispute = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const dispute = this.parseRequired(options.dispute, 'string', 'stripe.submitIssuingDispute: dispute is required.'); + delete options.dispute; + return stripe.issuing.disputes.submit(dispute, options,__extra); +}; + +// /v1/issuing/transactions - get +exports.listIssuingTransactions = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.issuing.transactions.list(options,__extra); +}; + +// /v1/issuing/transactions/{transaction} - get +exports.retrieveIssuingTransaction = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const transaction = this.parseRequired(options.transaction, 'string', 'stripe.retrieveIssuingTransaction: transaction is required.'); + delete options.transaction; + return stripe.issuing.transactions.retrieve(transaction, options,__extra); +}; + +// /v1/issuing/transactions/{transaction} - post +exports.updateIssuingTransaction = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const transaction = this.parseRequired(options.transaction, 'string', 'stripe.updateIssuingTransaction: transaction is required.'); + delete options.transaction; + return stripe.issuing.transactions.update(transaction, options,__extra); +}; + +// /v1/mandates/{mandate} - get +exports.retrieveMandate = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const mandate = this.parseRequired(options.mandate, 'string', 'stripe.retrieveMandate: mandate is required.'); + delete options.mandate; + return stripe.mandates.retrieve(mandate, options,__extra); +}; + +// /v1/orders - get +exports.listOrders = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.orders.list(options,__extra); +}; + +// /v1/orders - post +exports.createOrder = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.orders.create(options,__extra); +}; + +// /v1/orders/{id} - get +exports.retrieveOrder = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const id = this.parseRequired(options.id, 'string', 'stripe.retrieveOrder: id is required.'); + delete options.id; + return stripe.orders.retrieve(id, options,__extra); +}; + +// /v1/orders/{id} - post +exports.updateOrder = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const id = this.parseRequired(options.id, 'string', 'stripe.updateOrder: id is required.'); + delete options.id; + return stripe.orders.update(id, options,__extra); +}; + +// /v1/orders/{id}/cancel - post +exports.cancelOrder = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const id = this.parseRequired(options.id, 'string', 'stripe.cancelOrder: id is required.'); + delete options.id; + return stripe.orders.cancel(id, options,__extra); +}; + +// /v1/orders/{id}/line_items - get +exports.listOrderLineItems = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const id = this.parseRequired(options.id, 'string', 'stripe.listOrderLineItems: id is required.'); + delete options.id; + return stripe.orders.listLineItems(id, options,__extra); +}; + +// /v1/orders/{id}/reopen - post +exports.reopenOrder = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const id = this.parseRequired(options.id, 'string', 'stripe.reopenOrder: id is required.'); + delete options.id; + return stripe.orders.reopen(id, options,__extra); +}; + +// /v1/orders/{id}/submit - post +exports.submitOrder = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const id = this.parseRequired(options.id, 'string', 'stripe.submitOrder: id is required.'); + delete options.id; + return stripe.orders.submit(id, options,__extra); +}; + +// /v1/payment_intents - get +exports.listPaymentIntents = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.paymentIntents.list(options,__extra); +}; + +// /v1/payment_intents - post +exports.createPaymentIntent = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.paymentIntents.create(options,__extra); +}; + +// /v1/payment_intents/search - get +exports.searchPaymentIntents = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const query = this.parseRequired(options.query, 'string', 'stripe.searchPaymentIntents: query is required.'); + return stripe.paymentIntents.search(options,__extra); +}; + +// /v1/payment_intents/{intent} - get +exports.retrievePaymentIntent = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const intent = this.parseRequired(options.intent, 'string', 'stripe.retrievePaymentIntent: intent is required.'); + delete options.intent; + return stripe.paymentIntents.retrieve(intent, options,__extra); +}; + +// /v1/payment_intents/{intent} - post +exports.updatePaymentIntent = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const intent = this.parseRequired(options.intent, 'string', 'stripe.updatePaymentIntent: intent is required.'); + delete options.intent; + return stripe.paymentIntents.update(intent, options,__extra); +}; + +// /v1/payment_intents/{intent}/apply_customer_balance - post +exports.applyCustomerBalancePaymentIntent = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const intent = this.parseRequired(options.intent, 'string', 'stripe.applyCustomerBalancePaymentIntent: intent is required.'); + delete options.intent; + return stripe.paymentIntents.applyCustomerBalance(intent, options,__extra); +}; + +// /v1/payment_intents/{intent}/cancel - post +exports.cancelPaymentIntent = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const intent = this.parseRequired(options.intent, 'string', 'stripe.cancelPaymentIntent: intent is required.'); + delete options.intent; + return stripe.paymentIntents.cancel(intent, options,__extra); +}; + +// /v1/payment_intents/{intent}/capture - post +exports.capturePaymentIntent = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const intent = this.parseRequired(options.intent, 'string', 'stripe.capturePaymentIntent: intent is required.'); + delete options.intent; + return stripe.paymentIntents.capture(intent, options,__extra); +}; + +// /v1/payment_intents/{intent}/confirm - post +exports.confirmPaymentIntent = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const intent = this.parseRequired(options.intent, 'string', 'stripe.confirmPaymentIntent: intent is required.'); + delete options.intent; + return stripe.paymentIntents.confirm(intent, options,__extra); +}; + +// /v1/payment_intents/{intent}/increment_authorization - post +exports.incrementAuthorizationPaymentIntent = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const intent = this.parseRequired(options.intent, 'string', 'stripe.incrementAuthorizationPaymentIntent: intent is required.'); + delete options.intent; + return stripe.paymentIntents.incrementAuthorization(intent, options,__extra); +}; + +// /v1/payment_intents/{intent}/verify_microdeposits - post +exports.createPaymentIntentVerifyMicrodeposit = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const intent = this.parseRequired(options.intent, 'string', 'stripe.createPaymentIntentVerifyMicrodeposit: intent is required.'); + delete options.intent; + return stripe.paymentIntents.createVerifyMicrodeposit(intent, options,__extra); +}; + +// /v1/payment_links - get +exports.listPaymentLinks = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.paymentLinks.list(options,__extra); +}; + +// /v1/payment_links - post +exports.createPaymentLink = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.paymentLinks.create(options,__extra); +}; + +// /v1/payment_links/{payment_link} - get +exports.retrievePaymentLink = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const payment_link = this.parseRequired(options.payment_link, 'string', 'stripe.retrievePaymentLink: payment_link is required.'); + delete options.payment_link; + return stripe.paymentLinks.retrieve(payment_link, options,__extra); +}; + +// /v1/payment_links/{payment_link} - post +exports.updatePaymentLink = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const payment_link = this.parseRequired(options.payment_link, 'string', 'stripe.updatePaymentLink: payment_link is required.'); + delete options.payment_link; + return stripe.paymentLinks.update(payment_link, options,__extra); +}; + +// /v1/payment_links/{payment_link}/line_items - get +exports.listPaymentLinkLineItems = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const payment_link = this.parseRequired(options.payment_link, 'string', 'stripe.listPaymentLinkLineItems: payment_link is required.'); + delete options.payment_link; + return stripe.paymentLinks.listLineItems(payment_link, options,__extra); +}; + +// /v1/payment_methods - get +exports.listPaymentMethods = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const type = this.parseRequired(options.type, 'string', 'stripe.listPaymentMethods: type is required.'); + return stripe.paymentMethods.list(options,__extra); +}; + +// /v1/payment_methods - post +exports.createPaymentMethod = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.paymentMethods.create(options,__extra); +}; + +// /v1/payment_methods/{payment_method} - get +exports.retrievePaymentMethod = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const payment_method = this.parseRequired(options.payment_method, 'string', 'stripe.retrievePaymentMethod: payment_method is required.'); + delete options.payment_method; + return stripe.paymentMethods.retrieve(payment_method, options,__extra); +}; + +// /v1/payment_methods/{payment_method} - post +exports.updatePaymentMethod = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const payment_method = this.parseRequired(options.payment_method, 'string', 'stripe.updatePaymentMethod: payment_method is required.'); + delete options.payment_method; + return stripe.paymentMethods.update(payment_method, options,__extra); +}; + +// /v1/payment_methods/{payment_method}/attach - post +exports.attachPaymentMethod = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const payment_method = this.parseRequired(options.payment_method, 'string', 'stripe.attachPaymentMethod: payment_method is required.'); + delete options.payment_method; + return stripe.paymentMethods.attach(payment_method, options,__extra); +}; + +// /v1/payment_methods/{payment_method}/detach - post +exports.detachPaymentMethod = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const payment_method = this.parseRequired(options.payment_method, 'string', 'stripe.detachPaymentMethod: payment_method is required.'); + delete options.payment_method; + return stripe.paymentMethods.detach(payment_method, options,__extra); +}; + +// /v1/payouts - get +exports.listPayouts = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.payouts.list(options,__extra); +}; + +// /v1/payouts - post +exports.createPayout = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.payouts.create(options,__extra); +}; + +// /v1/payouts/{payout} - get +exports.retrievePayout = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const payout = this.parseRequired(options.payout, 'string', 'stripe.retrievePayout: payout is required.'); + delete options.payout; + return stripe.payouts.retrieve(payout, options,__extra); +}; + +// /v1/payouts/{payout} - post +exports.updatePayout = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const payout = this.parseRequired(options.payout, 'string', 'stripe.updatePayout: payout is required.'); + delete options.payout; + return stripe.payouts.update(payout, options,__extra); +}; + +// /v1/payouts/{payout}/cancel - post +exports.cancelPayout = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const payout = this.parseRequired(options.payout, 'string', 'stripe.cancelPayout: payout is required.'); + delete options.payout; + return stripe.payouts.cancel(payout, options,__extra); +}; + +// /v1/payouts/{payout}/reverse - post +exports.reversePayout = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const payout = this.parseRequired(options.payout, 'string', 'stripe.reversePayout: payout is required.'); + delete options.payout; + return stripe.payouts.reverse(payout, options,__extra); +}; + +// /v1/plans - get +exports.listPlans = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.plans.list(options,__extra); +}; + +// /v1/plans - post +exports.createPlan = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.plans.create(options,__extra); +}; + +// /v1/plans/{plan} - delete +exports.deletePlan = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const plan = this.parseRequired(options.plan, 'string', 'stripe.deletePlan: plan is required.'); + delete options.plan; + return stripe.plans.del(plan, options,__extra); +}; + +// /v1/plans/{plan} - get +exports.retrievePlan = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const plan = this.parseRequired(options.plan, 'string', 'stripe.retrievePlan: plan is required.'); + delete options.plan; + return stripe.plans.retrieve(plan, options,__extra); +}; + +// /v1/plans/{plan} - post +exports.updatePlan = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const plan = this.parseRequired(options.plan, 'string', 'stripe.updatePlan: plan is required.'); + delete options.plan; + return stripe.plans.update(plan, options,__extra); +}; + +// /v1/prices - get +exports.listPrices = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.prices.list(options,__extra); +}; + +// /v1/prices - post +exports.createPrice = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.prices.create(options,__extra); +}; + +// /v1/prices/search - get +exports.searchPrices = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const query = this.parseRequired(options.query, 'string', 'stripe.searchPrices: query is required.'); + return stripe.prices.search(options,__extra); +}; + +// /v1/prices/{price} - get +exports.retrievePrice = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const price = this.parseRequired(options.price, 'string', 'stripe.retrievePrice: price is required.'); + delete options.price; + return stripe.prices.retrieve(price, options,__extra); +}; + +// /v1/prices/{price} - post +exports.updatePrice = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const price = this.parseRequired(options.price, 'string', 'stripe.updatePrice: price is required.'); + delete options.price; + return stripe.prices.update(price, options,__extra); +}; + +// /v1/products - get +exports.listProducts = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.products.list(options,__extra); +}; + +// /v1/products - post +exports.createProduct = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.products.create(options,__extra); +}; + +// /v1/products/search - get +exports.searchProducts = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const query = this.parseRequired(options.query, 'string', 'stripe.searchProducts: query is required.'); + return stripe.products.search(options,__extra); +}; + +// /v1/products/{id} - delete +exports.deleteProduct = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const id = this.parseRequired(options.id, 'string', 'stripe.deleteProduct: id is required.'); + delete options.id; + return stripe.products.del(id, options,__extra); +}; + +// /v1/products/{id} - get +exports.retrieveProduct = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const id = this.parseRequired(options.id, 'string', 'stripe.retrieveProduct: id is required.'); + delete options.id; + return stripe.products.retrieve(id, options,__extra); +}; + +// /v1/products/{id} - post +exports.updateProduct = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const id = this.parseRequired(options.id, 'string', 'stripe.updateProduct: id is required.'); + delete options.id; + return stripe.products.update(id, options,__extra); +}; + +// /v1/promotion_codes - get +exports.listPromotionCodes = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.promotionCodes.list(options,__extra); +}; + +// /v1/promotion_codes - post +exports.createPromotionCode = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.promotionCodes.create(options,__extra); +}; + +// /v1/promotion_codes/{promotion_code} - get +exports.retrievePromotionCode = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const promotion_code = this.parseRequired(options.promotion_code, 'string', 'stripe.retrievePromotionCode: promotion_code is required.'); + delete options.promotion_code; + return stripe.promotionCodes.retrieve(promotion_code, options,__extra); +}; + +// /v1/promotion_codes/{promotion_code} - post +exports.updatePromotionCode = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const promotion_code = this.parseRequired(options.promotion_code, 'string', 'stripe.updatePromotionCode: promotion_code is required.'); + delete options.promotion_code; + return stripe.promotionCodes.update(promotion_code, options,__extra); +}; + +// /v1/quotes - get +exports.listQuotes = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.quotes.list(options,__extra); +}; + +// /v1/quotes - post +exports.createQuote = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.quotes.create(options,__extra); +}; + +// /v1/quotes/{quote} - get +exports.retrieveQuote = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const quote = this.parseRequired(options.quote, 'string', 'stripe.retrieveQuote: quote is required.'); + delete options.quote; + return stripe.quotes.retrieve(quote, options,__extra); +}; + +// /v1/quotes/{quote} - post +exports.updateQuote = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const quote = this.parseRequired(options.quote, 'string', 'stripe.updateQuote: quote is required.'); + delete options.quote; + return stripe.quotes.update(quote, options,__extra); +}; + +// /v1/quotes/{quote}/accept - post +exports.acceptQuote = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const quote = this.parseRequired(options.quote, 'string', 'stripe.acceptQuote: quote is required.'); + delete options.quote; + return stripe.quotes.accept(quote, options,__extra); +}; + +// /v1/quotes/{quote}/cancel - post +exports.cancelQuote = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const quote = this.parseRequired(options.quote, 'string', 'stripe.cancelQuote: quote is required.'); + delete options.quote; + return stripe.quotes.cancel(quote, options,__extra); +}; + +// /v1/quotes/{quote}/computed_upfront_line_items - get +exports.listQuoteComputedUpfrontLineItems = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const quote = this.parseRequired(options.quote, 'string', 'stripe.listQuoteComputedUpfrontLineItems: quote is required.'); + delete options.quote; + return stripe.quotes.listComputedUpfrontLineItems(quote, options,__extra); +}; + +// /v1/quotes/{quote}/finalize - post +exports.finalizeQuote = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const quote = this.parseRequired(options.quote, 'string', 'stripe.finalizeQuote: quote is required.'); + delete options.quote; + return stripe.quotes.finalizeQuote(quote, options,__extra); +}; + +// /v1/quotes/{quote}/line_items - get +exports.listQuoteLineItems = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const quote = this.parseRequired(options.quote, 'string', 'stripe.listQuoteLineItems: quote is required.'); + delete options.quote; + return stripe.quotes.listLineItems(quote, options,__extra); +}; + +// /v1/quotes/{quote}/pdf - get +exports.retrieveQuotePdf = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const quote = this.parseRequired(options.quote, 'string', 'stripe.retrieveQuotePdf: quote is required.'); + delete options.quote; + return stripe.quotes.retrievePdf(quote, options,__extra); +}; + +// /v1/radar/early_fraud_warnings - get +exports.listRadarEarlyFraudWarnings = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.radar.earlyFraudWarnings.list(options,__extra); +}; + +// /v1/radar/early_fraud_warnings/{early_fraud_warning} - get +exports.retrieveRadarEarlyFraudWarning = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const early_fraud_warning = this.parseRequired(options.early_fraud_warning, 'string', 'stripe.retrieveRadarEarlyFraudWarning: early_fraud_warning is required.'); + delete options.early_fraud_warning; + return stripe.radar.earlyFraudWarnings.retrieve(early_fraud_warning, options,__extra); +}; + +// /v1/radar/value_list_items - get +exports.listRadarValueListItems = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const value_list = this.parseRequired(options.value_list, 'string', 'stripe.listRadarValueListItems: value_list is required.'); + return stripe.radar.valueListItems.list(options,__extra); +}; + +// /v1/radar/value_list_items - post +exports.createRadarValueListItem = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.radar.valueListItems.create(options,__extra); +}; + +// /v1/radar/value_list_items/{item} - delete +exports.deleteRadarValueListItem = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const item = this.parseRequired(options.item, 'string', 'stripe.deleteRadarValueListItem: item is required.'); + delete options.item; + return stripe.radar.valueListItems.del(item, options,__extra); +}; + +// /v1/radar/value_list_items/{item} - get +exports.retrieveRadarValueListItem = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const item = this.parseRequired(options.item, 'string', 'stripe.retrieveRadarValueListItem: item is required.'); + delete options.item; + return stripe.radar.valueListItems.retrieve(item, options,__extra); +}; + +// /v1/radar/value_lists - get +exports.listRadarValueLists = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.radar.valueLists.list(options,__extra); +}; + +// /v1/radar/value_lists - post +exports.createRadarValueList = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.radar.valueLists.create(options,__extra); +}; + +// /v1/radar/value_lists/{value_list} - delete +exports.deleteRadarValueList = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const value_list = this.parseRequired(options.value_list, 'string', 'stripe.deleteRadarValueList: value_list is required.'); + delete options.value_list; + return stripe.radar.valueLists.del(value_list, options,__extra); +}; + +// /v1/radar/value_lists/{value_list} - get +exports.retrieveRadarValueList = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const value_list = this.parseRequired(options.value_list, 'string', 'stripe.retrieveRadarValueList: value_list is required.'); + delete options.value_list; + return stripe.radar.valueLists.retrieve(value_list, options,__extra); +}; + +// /v1/radar/value_lists/{value_list} - post +exports.updateRadarValueList = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const value_list = this.parseRequired(options.value_list, 'string', 'stripe.updateRadarValueList: value_list is required.'); + delete options.value_list; + return stripe.radar.valueLists.update(value_list, options,__extra); +}; + +// /v1/recipients - get +exports.listRecipients = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.recipients.list(options,__extra); +}; + +// /v1/recipients - post +exports.createRecipient = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.recipients.create(options,__extra); +}; + +// /v1/recipients/{id} - delete +exports.deleteRecipient = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const id = this.parseRequired(options.id, 'string', 'stripe.deleteRecipient: id is required.'); + delete options.id; + return stripe.recipients.del(id, options,__extra); +}; + +// /v1/recipients/{id} - get +exports.retrieveRecipient = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const id = this.parseRequired(options.id, 'string', 'stripe.retrieveRecipient: id is required.'); + delete options.id; + return stripe.recipients.retrieve(id, options,__extra); +}; + +// /v1/recipients/{id} - post +exports.updateRecipient = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const id = this.parseRequired(options.id, 'string', 'stripe.updateRecipient: id is required.'); + delete options.id; + return stripe.recipients.update(id, options,__extra); +}; + +// /v1/refunds - get +exports.listRefunds = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.refunds.list(options,__extra); +}; + +// /v1/refunds - post +exports.createRefund = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.refunds.create(options,__extra); +}; + +// /v1/refunds/{refund} - get +exports.retrieveRefund = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const refund = this.parseRequired(options.refund, 'string', 'stripe.retrieveRefund: refund is required.'); + delete options.refund; + return stripe.refunds.retrieve(refund, options,__extra); +}; + +// /v1/refunds/{refund} - post +exports.updateRefund = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const refund = this.parseRequired(options.refund, 'string', 'stripe.updateRefund: refund is required.'); + delete options.refund; + return stripe.refunds.update(refund, options,__extra); +}; + +// /v1/refunds/{refund}/cancel - post +exports.cancelRefund = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const refund = this.parseRequired(options.refund, 'string', 'stripe.cancelRefund: refund is required.'); + delete options.refund; + return stripe.refunds.cancel(refund, options,__extra); +}; + +// /v1/reporting/report_runs - get +exports.listReportingReportRuns = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.reporting.reportRuns.list(options,__extra); +}; + +// /v1/reporting/report_runs - post +exports.createReportingReportRun = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.reporting.reportRuns.create(options,__extra); +}; + +// /v1/reporting/report_runs/{report_run} - get +exports.retrieveReportingReportRun = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const report_run = this.parseRequired(options.report_run, 'string', 'stripe.retrieveReportingReportRun: report_run is required.'); + delete options.report_run; + return stripe.reporting.reportRuns.retrieve(report_run, options,__extra); +}; + +// /v1/reporting/report_types - get +exports.listReportingReportTypes = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.reporting.reportTypes.list(options,__extra); +}; + +// /v1/reporting/report_types/{report_type} - get +exports.retrieveReportingReportType = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const report_type = this.parseRequired(options.report_type, 'string', 'stripe.retrieveReportingReportType: report_type is required.'); + delete options.report_type; + return stripe.reporting.reportTypes.retrieve(report_type, options,__extra); +}; + +// /v1/reviews - get +exports.listReviews = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.reviews.list(options,__extra); +}; + +// /v1/reviews/{review} - get +exports.retrieveReview = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const review = this.parseRequired(options.review, 'string', 'stripe.retrieveReview: review is required.'); + delete options.review; + return stripe.reviews.retrieve(review, options,__extra); +}; + +// /v1/reviews/{review}/approve - post +exports.approveReview = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const review = this.parseRequired(options.review, 'string', 'stripe.approveReview: review is required.'); + delete options.review; + return stripe.reviews.approve(review, options,__extra); +}; + +// /v1/setup_attempts - get +exports.listSetupAttempts = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const setup_intent = this.parseRequired(options.setup_intent, 'string', 'stripe.listSetupAttempts: setup_intent is required.'); + return stripe.setupAttempts.list(options,__extra); +}; + +// /v1/setup_intents - get +exports.listSetupIntents = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.setupIntents.list(options,__extra); +}; + +// /v1/setup_intents - post +exports.createSetupIntent = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.setupIntents.create(options,__extra); +}; + +// /v1/setup_intents/{intent} - get +exports.retrieveSetupIntent = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const intent = this.parseRequired(options.intent, 'string', 'stripe.retrieveSetupIntent: intent is required.'); + delete options.intent; + return stripe.setupIntents.retrieve(intent, options,__extra); +}; + +// /v1/setup_intents/{intent} - post +exports.updateSetupIntent = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const intent = this.parseRequired(options.intent, 'string', 'stripe.updateSetupIntent: intent is required.'); + delete options.intent; + return stripe.setupIntents.update(intent, options,__extra); +}; + +// /v1/setup_intents/{intent}/cancel - post +exports.cancelSetupIntent = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const intent = this.parseRequired(options.intent, 'string', 'stripe.cancelSetupIntent: intent is required.'); + delete options.intent; + return stripe.setupIntents.cancel(intent, options,__extra); +}; + +// /v1/setup_intents/{intent}/confirm - post +exports.confirmSetupIntent = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const intent = this.parseRequired(options.intent, 'string', 'stripe.confirmSetupIntent: intent is required.'); + delete options.intent; + return stripe.setupIntents.confirm(intent, options,__extra); +}; + +// /v1/setup_intents/{intent}/verify_microdeposits - post +exports.createSetupIntentVerifyMicrodeposit = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const intent = this.parseRequired(options.intent, 'string', 'stripe.createSetupIntentVerifyMicrodeposit: intent is required.'); + delete options.intent; + return stripe.setupIntents.createVerifyMicrodeposit(intent, options,__extra); +}; + +// /v1/shipping_rates - get +exports.listShippingRates = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.shippingRates.list(options,__extra); +}; + +// /v1/shipping_rates - post +exports.createShippingRate = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.shippingRates.create(options,__extra); +}; + +// /v1/shipping_rates/{shipping_rate_token} - get +exports.retrieveShippingRate = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const shipping_rate_token = this.parseRequired(options.shipping_rate_token, 'string', 'stripe.retrieveShippingRate: shipping_rate_token is required.'); + delete options.shipping_rate_token; + return stripe.shippingRates.retrieve(shipping_rate_token, options,__extra); +}; + +// /v1/shipping_rates/{shipping_rate_token} - post +exports.updateShippingRate = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const shipping_rate_token = this.parseRequired(options.shipping_rate_token, 'string', 'stripe.updateShippingRate: shipping_rate_token is required.'); + delete options.shipping_rate_token; + return stripe.shippingRates.update(shipping_rate_token, options,__extra); +}; + +// /v1/sigma/scheduled_query_runs - get +exports.listSigmaScheduledQueryRuns = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.sigma.scheduledQueryRuns.list(options,__extra); +}; + +// /v1/sigma/scheduled_query_runs/{scheduled_query_run} - get +exports.retrieveSigmaScheduledQueryRun = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const scheduled_query_run = this.parseRequired(options.scheduled_query_run, 'string', 'stripe.retrieveSigmaScheduledQueryRun: scheduled_query_run is required.'); + delete options.scheduled_query_run; + return stripe.sigma.scheduledQueryRuns.retrieve(scheduled_query_run, options,__extra); +}; + +// /v1/skus - get +exports.listSkus = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.skus.list(options,__extra); +}; + +// /v1/skus - post +exports.createSku = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.skus.create(options,__extra); +}; + +// /v1/skus/{id} - delete +exports.deleteSku = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const id = this.parseRequired(options.id, 'string', 'stripe.deleteSku: id is required.'); + delete options.id; + return stripe.skus.del(id, options,__extra); +}; + +// /v1/skus/{id} - get +exports.retrieveSku = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const id = this.parseRequired(options.id, 'string', 'stripe.retrieveSku: id is required.'); + delete options.id; + return stripe.skus.retrieve(id, options,__extra); +}; + +// /v1/skus/{id} - post +exports.updateSku = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const id = this.parseRequired(options.id, 'string', 'stripe.updateSku: id is required.'); + delete options.id; + return stripe.skus.update(id, options,__extra); +}; + +// /v1/sources - post +exports.createSource = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.sources.create(options,__extra); +}; + +// /v1/sources/{source} - get +exports.retrieveSource = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const source = this.parseRequired(options.source, 'string', 'stripe.retrieveSource: source is required.'); + delete options.source; + return stripe.sources.retrieve(source, options,__extra); +}; + +// /v1/sources/{source} - post +exports.updateSource = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const source = this.parseRequired(options.source, 'string', 'stripe.updateSource: source is required.'); + delete options.source; + return stripe.sources.update(source, options,__extra); +}; + +// /v1/sources/{source}/source_transactions - get +exports.listSourceSourceTransactions = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const source = this.parseRequired(options.source, 'string', 'stripe.listSourceSourceTransactions: source is required.'); + delete options.source; + return stripe.sources.listSourceTransactions(source, options,__extra); +}; + +// /v1/sources/{source}/verify - post +exports.verifySource = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const source = this.parseRequired(options.source, 'string', 'stripe.verifySource: source is required.'); + delete options.source; + return stripe.sources.verify(source, options,__extra); +}; + +// /v1/subscription_items - get +exports.listSubscriptionItems = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const subscription = this.parseRequired(options.subscription, 'string', 'stripe.listSubscriptionItems: subscription is required.'); + return stripe.subscriptionItems.list(options,__extra); +}; + +// /v1/subscription_items - post +exports.createSubscriptionItem = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.subscriptionItems.create(options,__extra); +}; + +// /v1/subscription_items/{item} - delete +exports.deleteSubscriptionItem = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const item = this.parseRequired(options.item, 'string', 'stripe.deleteSubscriptionItem: item is required.'); + delete options.item; + return stripe.subscriptionItems.del(item, options,__extra); +}; + +// /v1/subscription_items/{item} - get +exports.retrieveSubscriptionItem = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const item = this.parseRequired(options.item, 'string', 'stripe.retrieveSubscriptionItem: item is required.'); + delete options.item; + return stripe.subscriptionItems.retrieve(item, options,__extra); +}; + +// /v1/subscription_items/{item} - post +exports.updateSubscriptionItem = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const item = this.parseRequired(options.item, 'string', 'stripe.updateSubscriptionItem: item is required.'); + delete options.item; + return stripe.subscriptionItems.update(item, options,__extra); +}; + +// /v1/subscription_items/{subscription_item}/usage_record_summaries - get +exports.listSubscriptionItemUsageRecordSummaries = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const subscription_item = this.parseRequired(options.subscription_item, 'string', 'stripe.listSubscriptionItemUsageRecordSummaries: subscription_item is required.'); + delete options.subscription_item; + return stripe.subscriptionItems.listUsageRecordSummaries(subscription_item, options,__extra); +}; + +// /v1/subscription_items/{subscription_item}/usage_records - post +exports.createSubscriptionItemUsageRecord = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const subscription_item = this.parseRequired(options.subscription_item, 'string', 'stripe.createSubscriptionItemUsageRecord: subscription_item is required.'); + delete options.subscription_item; + return stripe.subscriptionItems.createUsageRecord(subscription_item, options,__extra); +}; + +// /v1/subscription_schedules - get +exports.listSubscriptionSchedules = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.subscriptionSchedules.list(options,__extra); +}; + +// /v1/subscription_schedules - post +exports.createSubscriptionSchedule = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.subscriptionSchedules.create(options,__extra); +}; + +// /v1/subscription_schedules/{schedule} - get +exports.retrieveSubscriptionSchedule = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const schedule = this.parseRequired(options.schedule, 'string', 'stripe.retrieveSubscriptionSchedule: schedule is required.'); + delete options.schedule; + return stripe.subscriptionSchedules.retrieve(schedule, options,__extra); +}; + +// /v1/subscription_schedules/{schedule} - post +exports.updateSubscriptionSchedule = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const schedule = this.parseRequired(options.schedule, 'string', 'stripe.updateSubscriptionSchedule: schedule is required.'); + delete options.schedule; + return stripe.subscriptionSchedules.update(schedule, options,__extra); +}; + +// /v1/subscription_schedules/{schedule}/cancel - post +exports.cancelSubscriptionSchedule = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const schedule = this.parseRequired(options.schedule, 'string', 'stripe.cancelSubscriptionSchedule: schedule is required.'); + delete options.schedule; + return stripe.subscriptionSchedules.cancel(schedule, options,__extra); +}; + +// /v1/subscription_schedules/{schedule}/release - post +exports.releaseSubscriptionSchedule = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const schedule = this.parseRequired(options.schedule, 'string', 'stripe.releaseSubscriptionSchedule: schedule is required.'); + delete options.schedule; + return stripe.subscriptionSchedules.release(schedule, options,__extra); +}; + +// /v1/subscriptions - get +exports.listSubscriptions = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.subscriptions.list(options,__extra); +}; + +// /v1/subscriptions - post +exports.createSubscription = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.subscriptions.create(options,__extra); +}; + +// /v1/subscriptions/search - get +exports.searchSubscriptions = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const query = this.parseRequired(options.query, 'string', 'stripe.searchSubscriptions: query is required.'); + return stripe.subscriptions.search(options,__extra); +}; + +// /v1/subscriptions/{subscription_exposed_id} - delete +exports.deleteSubscription = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const subscription_exposed_id = this.parseRequired(options.subscription_exposed_id, 'string', 'stripe.deleteSubscription: subscription_exposed_id is required.'); + delete options.subscription_exposed_id; + return stripe.subscriptions.del(subscription_exposed_id, options,__extra); +}; + +// /v1/subscriptions/{subscription_exposed_id} - get +exports.retrieveSubscription = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const subscription_exposed_id = this.parseRequired(options.subscription_exposed_id, 'string', 'stripe.retrieveSubscription: subscription_exposed_id is required.'); + delete options.subscription_exposed_id; + return stripe.subscriptions.retrieve(subscription_exposed_id, options,__extra); +}; + +// /v1/subscriptions/{subscription_exposed_id} - post +exports.updateSubscription = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const subscription_exposed_id = this.parseRequired(options.subscription_exposed_id, 'string', 'stripe.updateSubscription: subscription_exposed_id is required.'); + delete options.subscription_exposed_id; + return stripe.subscriptions.update(subscription_exposed_id, options,__extra); +}; + +// /v1/subscriptions/{subscription_exposed_id}/discount - delete +exports.deleteSubscriptionDiscount = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const subscription_exposed_id = this.parseRequired(options.subscription_exposed_id, 'string', 'stripe.deleteSubscriptionDiscount: subscription_exposed_id is required.'); + delete options.subscription_exposed_id; + return stripe.subscriptions.deleteDiscount(subscription_exposed_id, options,__extra); +}; + +// /v1/tax_codes - get +exports.listTaxCodes = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.taxCodes.list(options,__extra); +}; + +// /v1/tax_codes/{id} - get +exports.retrieveTaxCode = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const id = this.parseRequired(options.id, 'string', 'stripe.retrieveTaxCode: id is required.'); + delete options.id; + return stripe.taxCodes.retrieve(id, options,__extra); +}; + +// /v1/tax_rates - get +exports.listTaxRates = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.taxRates.list(options,__extra); +}; + +// /v1/tax_rates - post +exports.createTaxRate = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.taxRates.create(options,__extra); +}; + +// /v1/tax_rates/{tax_rate} - get +exports.retrieveTaxRate = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const tax_rate = this.parseRequired(options.tax_rate, 'string', 'stripe.retrieveTaxRate: tax_rate is required.'); + delete options.tax_rate; + return stripe.taxRates.retrieve(tax_rate, options,__extra); +}; + +// /v1/tax_rates/{tax_rate} - post +exports.updateTaxRate = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const tax_rate = this.parseRequired(options.tax_rate, 'string', 'stripe.updateTaxRate: tax_rate is required.'); + delete options.tax_rate; + return stripe.taxRates.update(tax_rate, options,__extra); +}; + +// /v1/terminal/configurations - get +exports.listTerminalConfigurations = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.terminal.configurations.list(options,__extra); +}; + +// /v1/terminal/configurations - post +exports.createTerminalConfiguration = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.terminal.configurations.create(options,__extra); +}; + +// /v1/terminal/configurations/{configuration} - delete +exports.deleteTerminalConfiguration = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const configuration = this.parseRequired(options.configuration, 'string', 'stripe.deleteTerminalConfiguration: configuration is required.'); + delete options.configuration; + return stripe.terminal.configurations.del(configuration, options,__extra); +}; + +// /v1/terminal/configurations/{configuration} - get +exports.retrieveTerminalConfiguration = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const configuration = this.parseRequired(options.configuration, 'string', 'stripe.retrieveTerminalConfiguration: configuration is required.'); + delete options.configuration; + return stripe.terminal.configurations.retrieve(configuration, options,__extra); +}; + +// /v1/terminal/configurations/{configuration} - post +exports.updateTerminalConfiguration = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const configuration = this.parseRequired(options.configuration, 'string', 'stripe.updateTerminalConfiguration: configuration is required.'); + delete options.configuration; + return stripe.terminal.configurations.update(configuration, options,__extra); +}; + +// /v1/terminal/connection_tokens - post +exports.createTerminalConnectionToken = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.terminal.connectionTokens.create(options,__extra); +}; + +// /v1/terminal/locations - get +exports.listTerminalLocations = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.terminal.locations.list(options,__extra); +}; + +// /v1/terminal/locations - post +exports.createTerminalLocation = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.terminal.locations.create(options,__extra); +}; + +// /v1/terminal/locations/{location} - delete +exports.deleteTerminalLocation = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const location = this.parseRequired(options.location, 'string', 'stripe.deleteTerminalLocation: location is required.'); + delete options.location; + return stripe.terminal.locations.del(location, options,__extra); +}; + +// /v1/terminal/locations/{location} - get +exports.retrieveTerminalLocation = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const location = this.parseRequired(options.location, 'string', 'stripe.retrieveTerminalLocation: location is required.'); + delete options.location; + return stripe.terminal.locations.retrieve(location, options,__extra); +}; + +// /v1/terminal/locations/{location} - post +exports.updateTerminalLocation = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const location = this.parseRequired(options.location, 'string', 'stripe.updateTerminalLocation: location is required.'); + delete options.location; + return stripe.terminal.locations.update(location, options,__extra); +}; + +// /v1/terminal/readers - get +exports.listTerminalReaders = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.terminal.readers.list(options,__extra); +}; + +// /v1/terminal/readers - post +exports.createTerminalReader = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.terminal.readers.create(options,__extra); +}; + +// /v1/terminal/readers/{reader} - delete +exports.deleteTerminalReader = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const reader = this.parseRequired(options.reader, 'string', 'stripe.deleteTerminalReader: reader is required.'); + delete options.reader; + return stripe.terminal.readers.del(reader, options,__extra); +}; + +// /v1/terminal/readers/{reader} - get +exports.retrieveTerminalReader = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const reader = this.parseRequired(options.reader, 'string', 'stripe.retrieveTerminalReader: reader is required.'); + delete options.reader; + return stripe.terminal.readers.retrieve(reader, options,__extra); +}; + +// /v1/terminal/readers/{reader} - post +exports.updateTerminalReader = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const reader = this.parseRequired(options.reader, 'string', 'stripe.updateTerminalReader: reader is required.'); + delete options.reader; + return stripe.terminal.readers.update(reader, options,__extra); +}; + +// /v1/terminal/readers/{reader}/cancel_action - post +exports.cancelActionTerminalReader = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const reader = this.parseRequired(options.reader, 'string', 'stripe.cancelActionTerminalReader: reader is required.'); + delete options.reader; + return stripe.terminal.readers.cancelAction(reader, options,__extra); +}; + +// /v1/terminal/readers/{reader}/process_payment_intent - post +exports.processPaymentIntentTerminalReader = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const reader = this.parseRequired(options.reader, 'string', 'stripe.processPaymentIntentTerminalReader: reader is required.'); + delete options.reader; + return stripe.terminal.readers.processPaymentIntent(reader, options,__extra); +}; + +// /v1/terminal/readers/{reader}/process_setup_intent - post +exports.processSetupIntentTerminalReader = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const reader = this.parseRequired(options.reader, 'string', 'stripe.processSetupIntentTerminalReader: reader is required.'); + delete options.reader; + return stripe.terminal.readers.processSetupIntent(reader, options,__extra); +}; + +// /v1/terminal/readers/{reader}/set_reader_display - post +exports.setReaderDisplayTerminalReader = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const reader = this.parseRequired(options.reader, 'string', 'stripe.setReaderDisplayTerminalReader: reader is required.'); + delete options.reader; + return stripe.terminal.readers.setReaderDisplay(reader, options,__extra); +}; + +// /v1/test_helpers/refunds/{refund}/expire - post +exports.expireTestHelpersRefund = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const refund = this.parseRequired(options.refund, 'string', 'stripe.expireTestHelpersRefund: refund is required.'); + delete options.refund; + return stripe.testHelpers.refunds.expire(refund, options,__extra); +}; + +// /v1/test_helpers/terminal/readers/{reader}/present_payment_method - post +exports.presentPaymentMethodTestHelpersTerminalReader = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const reader = this.parseRequired(options.reader, 'string', 'stripe.presentPaymentMethodTestHelpersTerminalReader: reader is required.'); + delete options.reader; + return stripe.testHelpers.terminal.readers.presentPaymentMethod(reader, options,__extra); +}; + +// /v1/test_helpers/test_clocks - get +exports.listTestHelpersTestClocks = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.testHelpers.testClocks.list(options,__extra); +}; + +// /v1/test_helpers/test_clocks - post +exports.createTestHelpersTestClock = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.testHelpers.testClocks.create(options,__extra); +}; + +// /v1/test_helpers/test_clocks/{test_clock} - delete +exports.deleteTestHelpersTestClock = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const test_clock = this.parseRequired(options.test_clock, 'string', 'stripe.deleteTestHelpersTestClock: test_clock is required.'); + delete options.test_clock; + return stripe.testHelpers.testClocks.del(test_clock, options,__extra); +}; + +// /v1/test_helpers/test_clocks/{test_clock} - get +exports.retrieveTestHelpersTestClock = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const test_clock = this.parseRequired(options.test_clock, 'string', 'stripe.retrieveTestHelpersTestClock: test_clock is required.'); + delete options.test_clock; + return stripe.testHelpers.testClocks.retrieve(test_clock, options,__extra); +}; + +// /v1/test_helpers/test_clocks/{test_clock}/advance - post +exports.advanceTestHelpersTestClock = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const test_clock = this.parseRequired(options.test_clock, 'string', 'stripe.advanceTestHelpersTestClock: test_clock is required.'); + delete options.test_clock; + return stripe.testHelpers.testClocks.advance(test_clock, options,__extra); +}; + +// /v1/tokens - post +exports.createToken = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.tokens.create(options,__extra); +}; + +// /v1/tokens/{token} - get +exports.retrieveToken = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const token = this.parseRequired(options.token, 'string', 'stripe.retrieveToken: token is required.'); + delete options.token; + return stripe.tokens.retrieve(token, options,__extra); +}; + +// /v1/topups - get +exports.listTopups = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.topups.list(options,__extra); +}; + +// /v1/topups - post +exports.createTopup = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.topups.create(options,__extra); +}; + +// /v1/topups/{topup} - get +exports.retrieveTopup = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const topup = this.parseRequired(options.topup, 'string', 'stripe.retrieveTopup: topup is required.'); + delete options.topup; + return stripe.topups.retrieve(topup, options,__extra); +}; + +// /v1/topups/{topup} - post +exports.updateTopup = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const topup = this.parseRequired(options.topup, 'string', 'stripe.updateTopup: topup is required.'); + delete options.topup; + return stripe.topups.update(topup, options,__extra); +}; + +// /v1/topups/{topup}/cancel - post +exports.cancelTopup = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const topup = this.parseRequired(options.topup, 'string', 'stripe.cancelTopup: topup is required.'); + delete options.topup; + return stripe.topups.cancel(topup, options,__extra); +}; + +// /v1/transfers - get +exports.listTransfers = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.transfers.list(options,__extra); +}; + +// /v1/transfers - post +exports.createTransfer = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.transfers.create(options,__extra); +}; + +// /v1/transfers/{id}/reversals - get +exports.listTransferReversals = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const id = this.parseRequired(options.id, 'string', 'stripe.listTransferReversals: id is required.'); + delete options.id; + return stripe.transfers.listReversals(id, options,__extra); +}; + +// /v1/transfers/{id}/reversals - post +exports.createTransferReversal = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const id = this.parseRequired(options.id, 'string', 'stripe.createTransferReversal: id is required.'); + delete options.id; + return stripe.transfers.createReversal(id, options,__extra); +}; + +// /v1/transfers/{transfer} - get +exports.retrieveTransfer = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const transfer = this.parseRequired(options.transfer, 'string', 'stripe.retrieveTransfer: transfer is required.'); + delete options.transfer; + return stripe.transfers.retrieve(transfer, options,__extra); +}; + +// /v1/transfers/{transfer} - post +exports.updateTransfer = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const transfer = this.parseRequired(options.transfer, 'string', 'stripe.updateTransfer: transfer is required.'); + delete options.transfer; + return stripe.transfers.update(transfer, options,__extra); +}; + +// /v1/transfers/{transfer}/reversals/{id} - get +exports.retrieveTransferReversal = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const id = this.parseRequired(options.id, 'string', 'stripe.retrieveTransferReversal: id is required.'); + delete options.id; + const transfer = this.parseRequired(options.transfer, 'string', 'stripe.retrieveTransferReversal: transfer is required.'); + delete options.transfer; + return stripe.transfers.retrieveReversal(id, transfer, options,__extra); +}; + +// /v1/transfers/{transfer}/reversals/{id} - post +exports.updateTransferReversal = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const id = this.parseRequired(options.id, 'string', 'stripe.updateTransferReversal: id is required.'); + delete options.id; + const transfer = this.parseRequired(options.transfer, 'string', 'stripe.updateTransferReversal: transfer is required.'); + delete options.transfer; + return stripe.transfers.updateReversal(id, transfer, options,__extra); +}; + +// /v1/webhook_endpoints - get +exports.listWebhookEndpoints = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.webhookEndpoints.list(options,__extra); +}; + +// /v1/webhook_endpoints - post +exports.createWebhookEndpoint = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + return stripe.webhookEndpoints.create(options,__extra); +}; + +// /v1/webhook_endpoints/{webhook_endpoint} - delete +exports.deleteWebhookEndpoint = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const webhook_endpoint = this.parseRequired(options.webhook_endpoint, 'string', 'stripe.deleteWebhookEndpoint: webhook_endpoint is required.'); + delete options.webhook_endpoint; + return stripe.webhookEndpoints.del(webhook_endpoint, options,__extra); +}; + +// /v1/webhook_endpoints/{webhook_endpoint} - get +exports.retrieveWebhookEndpoint = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const webhook_endpoint = this.parseRequired(options.webhook_endpoint, 'string', 'stripe.retrieveWebhookEndpoint: webhook_endpoint is required.'); + delete options.webhook_endpoint; + return stripe.webhookEndpoints.retrieve(webhook_endpoint, options,__extra); +}; + +// /v1/webhook_endpoints/{webhook_endpoint} - post +exports.updateWebhookEndpoint = function(options) { + options = this.parse(options); + var __extra = options.__extra; + if (options.__extra) { + __extra = convertExtraOptions(__extra); + delete options.__extra; + } + + const webhook_endpoint = this.parseRequired(options.webhook_endpoint, 'string', 'stripe.updateWebhookEndpoint: webhook_endpoint is required.'); + delete options.webhook_endpoint; + return stripe.webhookEndpoints.update(webhook_endpoint, options,__extra); +}; diff --git a/lib/modules/upload.js b/lib/modules/upload.js new file mode 100644 index 0000000..feb5b79 --- /dev/null +++ b/lib/modules/upload.js @@ -0,0 +1,118 @@ +const fs = require('fs-extra'); +const debug = require('debug')('server-connect:upload'); +const { join, basename, extname } = require('path'); +const { toAppPath, toSystemPath, toSiteUrl, parseTemplate, getUniqFile } = require('../core/path'); +const diacritics = require('../core/diacritics'); + +module.exports = { + + upload: async function(options) { + let self = this; + let fields = this.parse(options.fields || this.parse('{{$_POST}}')); + let path = this.parseOptional(options.path, 'string', '/uploads'); + let overwrite = this.parseOptional(options.overwrite, 'boolean', false); + let createPath = this.parseOptional(options.createPath, 'boolean', true); + let throwErrors = this.parseOptional(options.throwErrors, 'boolean', false); + let template = typeof options.template == 'string' ? options.template : false; //this.parseOptional(options.template, 'string', ''); + let replaceSpace = this.parseOptional(options.replaceSpace, 'boolean', false); + let asciiOnly = this.parseOptional(options.asciiOnly, 'boolean', false); + let replaceDiacritics = this.parseOptional(options.replaceDiacritics, 'boolean', false); + + if (throwErrors) { + for (let field in this.req.files) { + if (Array.isArray(this.req.files[field])) { + for (let file of this.req.files[field]) { + if (file.truncated) { + throw new Error('Some files failed to upload.'); + } + } + } else { + let file = this.req.files[field]; + if (file.truncated) { + throw new Error('Some files failed to upload.'); + } + } + } + } + + path = toSystemPath(path); + + if (!fs.existsSync(path)) { + if (createPath) { + await fs.ensureDir(path); + } else { + throw new Error(`Upload path doesn't exist.`); + } + } + + let files = this.req.files; + let uploaded = []; + + if (files) { + await processFields(fields); + } + + return typeof fields == 'string' ? (uploaded.length ? uploaded[0] : null) : uploaded; + + async function processFields(fields) { + debug('Process fields: %O', fields); + + if (typeof fields == 'object') { + for (let i in fields) { + await processFields(fields[i]); + } + } else if (typeof fields == 'string' && files[fields]) { + let processing = files[fields]; + + if (!Array.isArray(processing)) processing = [processing]; + + for (let file of processing) { + debug('Processing file: %O', file); + + if (!file.processed) { + let name = file.name.replace(/[\x00-\x1f\x7f!%&#@$*()?:,;"'<>^`|+={}\[\]\\\/]/g, ''); + + if (replaceSpace) name = name.replace(/\s+/g, '_'); + if (replaceDiacritics) name = diacritics.replace(name); + if (asciiOnly) name = name.replace(/[^\x00-\x7e]/g, ''); + + let filepath = join(path, name); + + if (template) { + let _template = self.parse(template, self.scope.create({ + file: basename(filepath), + name: basename(filepath, extname(filepath)), + ext: extname(filepath) + })); + filepath = parseTemplate(filepath, _template); + } + + if (fs.existsSync(filepath)) { + if (overwrite) { + await fs.unlink(filepath); + } else { + filepath = getUniqFile(filepath); + } + } + + await file.mv(filepath); + + uploaded.push({ + name: basename(filepath), + path: toAppPath(filepath), + url: toSiteUrl(filepath), + type: file.mimetype, + size: file.size + }); + + file.processed = true; + } + } + } + + return uploaded; + } + + } + +}; \ No newline at end of file diff --git a/lib/modules/validator.js b/lib/modules/validator.js new file mode 100644 index 0000000..2c865a1 --- /dev/null +++ b/lib/modules/validator.js @@ -0,0 +1,18 @@ +const validator = require('../validator'); + +module.exports = { + + validate: function(options) { + const data = this.parse(options.data); + const noError = this.parseOptional(options.noError, 'boolean', false); + + return validator.validateData(this, data, noError); + }, + + error: function(options, name) { + const message = this.parseRequired(options.message, 'string', 'validator.error: message is required.'); + const error = { [options.fieldName ? 'form' : 'data']: {[options.fieldName || name]: message }}; + this.res.status(400).json(error); + }, + +}; \ No newline at end of file diff --git a/lib/modules/zip.js b/lib/modules/zip.js new file mode 100644 index 0000000..5abd1f7 --- /dev/null +++ b/lib/modules/zip.js @@ -0,0 +1,114 @@ +const fs = require('fs-extra'); +const debug = require('debug')('server-connect:zip'); +const { basename } = require('path'); +const { toSystemPath, toAppPath, getFilesArray, getUniqFile } = require('../core/path'); +const openZip = (zipfile) => require('unzipper').Open.file(zipfile); + +const Zip = function(zipfile, options) { + this.output = fs.createWriteStream(zipfile); + this.archive = require('archiver')('zip', options); + this.archive.on('warning', (err) => { + if (err.code == 'ENOENT') { + debug('error: %O', err); + } else { + throw err; + } + }); + this.archive.on('error', (err) => { + throw err; + }); + this.archive.pipe(this.output); +}; + +Zip.prototype.addFile = function(file) { + this.archive.file(file, { name: basename(file) }); +}; + +Zip.prototype.addDir = function(dir, recursive) { + if (recursive) { + this.archive.directory(dir, false); + } else { + this.archive.glob('*', { cwd: dir }); + } +}; + +Zip.prototype.save = function() { + return new Promise((resolve, reject) => { + this.output.on('close', resolve); + this.archive.on('error', reject); + this.archive.finalize(); + }); +}; + +module.exports = { + + zip: async function(options) { + let zipfile = toSystemPath(this.parseRequired(options.zipfile, 'string', 'zip.zip: zipfile is required.')); + let files = getFilesArray(this.parseRequired(options.files, 'object', 'zip.zip: files is requires.')); + let overwrite = this.parseOptional(options.overwrite, 'boolean', false); + let comment = this.parseOptional(options.comment, 'string', ''); + + if (!overwrite) zipfile = getUniqFile(zipfile); + + const zip = new Zip(zipfile, { comment }); + + for (let file of files) { + zip.addFile(file); + } + + await zip.save(); + + return toAppPath(zipfile); + }, + + zipdir: async function(options) { + let zipfile = toSystemPath(this.parseRequired(options.zipfile, 'string', 'zip.zipdir: zipfile is required.')); + let path = toSystemPath(this.parseRequired(options.path, 'string', 'zip.zipdir: path is required.')); + let overwrite = this.parseOptional(options.overwrite, 'boolean', false); + let recursive = this.parseOptional(options.recursive, 'boolean', false); + let comment = this.parseOptional(options.comment, 'string', ''); + + if (!overwrite) zipfile = getUniqFile(zipfile); + + const zip = new Zip(zipfile, { comment }); + + zip.addDir(path, recursive); + + await zip.save(); + + return toAppPath(zipfile); + }, + + unzip: async function(options) { + let zipfile = toSystemPath(this.parseRequired(options.zipfile, 'string', 'zip.unzip: zipfile is required.')); + let dest = toSystemPath(this.parseRequired(options.destination, 'string', 'zip.unzip: destination is required.')); + let overwrite = this.parseOptional(options.overwrite, 'boolean', true); + + // TODO: overwrite option + await openZip(zipfile).then(d => d.extract({ path: dest, concurrency: 4 })); + + return true; + }, + + dir: async function(options) { + let zipfile = toSystemPath(this.parseRequired(options.zipfile, 'string', 'zip.dir: zipfile is required.')); + + return openZip(zipfile).then(d => { + return d.files.map(file => ({ + type: file.type == 'Directory' ? 'dir': 'file', + path: file.path, + size: file.uncompressedSize, + compressedSize: file.compressedSize, + compressionMethod: file.compressionMethod == 8 ? 'Deflate' : 'None', + lastModified: file.lastModifiedDateTime + })); + }); + }, + + comment: async function(options) { + let zipfile = toSystemPath(this.parseRequired(options.zipfile, 'string', 'zip.comment: zipfile is required.')); + + return openZip(zipfile).then(d => d.comment); + }, + +}; \ No newline at end of file diff --git a/lib/oauth/index.js b/lib/oauth/index.js new file mode 100644 index 0000000..e80a480 --- /dev/null +++ b/lib/oauth/index.js @@ -0,0 +1,170 @@ +const { http, https } = require('follow-redirects'); +const querystring = require('querystring'); +const services = require('./services'); +const crypto = require('crypto'); + +class OAuth2 { + + constructor(app, opts, name) { + this.app = app; + this.name = name; + this.opts = opts; + + if (opts.service) { + Object.assign(this.opts, services[opts.service]); + } + + this.access_token = opts.access_token || app.getSession(`${name}_access_token`); + this.refresh_token = opts.refresh_token || app.getSession(`${name}_refresh_token`); + + if (this.access_token) { + let expires = app.getSession(`${name}_expires`); + + if (expires && expires < expires_in(-10)) { + this.access_token = null; + + app.removeSession(`${name}_access_token`); + app.removeSession(`${name}_expires`); + + if (this.refresh_token) { + this.refreshToken(this.refresh_token); + } + } + } + } + + async init() { + if (this.opts.jwt_bearer && !this.access_token) { + const assertion = this.app.getJSONWebToken(this.opts.jwt_bearer); + let response = await this.grant('urn:ietf:params:oauth:grant-type:jwt-bearer', { assertion }); + this.access_token = response.access_token; + } + + if (this.opts.client_credentials && !this.access_token) { + let response = await this.grant('client_credentials'); + this.access_token = response.access_token; + } + } + + async authorize(scopes = [], params = {}) { + let query = this.app.req.query; + + if (query.state && query.state == this.app.getSession(`${this.name}_state`)) { + this.app.removeSession(`${this.name}_state`); + + if (query.error) { + throw new Error(query.error_message || query.error); + } + + if (query.code) { + let params = { + redirect_uri: redirect_uri(this.app.req), + code: query.code + }; + + if (this.app.getSession(`${this.name}_code_verifier`)) { + params.code_verifier = this.app.getSession(`${this.name}_code_verifier`); + } + + return this.grant('authorization_code', params); + } + } + + if (this.opts.pkce || params.code_verifier) { + const code_verifier = params.code_verifier || crypto.randomBytes(40).toString('hex'); + this.app.setSession(`${this.name}_code_verifier`, code_verifier); + params.code_challenge_method = 'S256'; + params.code_challenge = crypto.createHash('sha256').update(code_verifier).digest('base64').replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_'); + } + + params = Object.assign({}, { + response_type: 'code', + client_id: this.opts.client_id, + scope: scopes.join(this.opts.scope_separator), + redirect_uri: redirect_uri(this.app.req), + state: generate_state() + }, this.opts.params, params); + + this.app.setSession(`${this.name}_state`, params.state); + + this.app.res.redirect(build_url(this.opts.auth_endpoint, params)); + } + + async refreshToken(refresh_token) { + return this.grant('refresh_token', { refresh_token }); + } + + async grant(type, params) { + const endpoint = new URL(this.opts.token_endpoint); + + return new Promise((resolve, reject) => { + const req = (endpoint.protocol == 'https:' ? https : http).request(endpoint, { + method: 'POST' + }, res => { + let body = ''; + res.setEncoding('utf8'); + res.on('data', chunk => body += chunk); + res.on('end', () => { + if (res.statusCode >= 400) { + return reject(new Error(`Http status code ${res.statusCode}. ${body}`)); + } + + try { + body = JSON.parse(body); + } catch (e) { } + + if (!body.access_token) { + return reject(new Error(`Http response has no access_token. ${JSON.stringify(body)}`)) + } + + this.app.setSession(`${this.name}_access_token`, body.access_token); + this.access_token = body.access_token; + + if (body.expires_in) { + this.app.setSession(`${this.name}_expires`, expires_in(body.expires_in)); + } + + if (body.refresh_token) { + this.app.setSession(`${this.name}_refresh_token`, body.refresh_token); + this.refresh_token = body.refresh_token; + } + + resolve(body); + }); + }); + + const body = querystring.stringify(Object.assign({ + grant_type: type, + client_id: this.opts.client_id, + client_secret: this.opts.client_secret + }, params)); + + req.on('error', reject); + req.setHeader('Content-Type', 'application/x-www-form-urlencoded'); + req.setHeader('Content-Length', body.length); // required by azure endpoint + req.write(body); + req.end(); + }); + } + +} + +function build_url(url, params) { + return url + (url.indexOf('?') != -1 ? '&' : '?') + querystring.stringify(params); +} + +function redirect_uri(req) { + let hasProxy = !!req.get('x-forwarded-host'); + return `${req.protocol}://${hasProxy ? req.hostname : req.get('host')}${req.path}`; +} + +function expires_in(s) { + return ~~(Date.now() / 1000) + s; +} + +function generate_state() { + const { v4: uuidv4 } = require('uuid'); + return uuidv4(); +} + +module.exports = OAuth2; \ No newline at end of file diff --git a/lib/oauth/services.js b/lib/oauth/services.js new file mode 100644 index 0000000..f23dda7 --- /dev/null +++ b/lib/oauth/services.js @@ -0,0 +1,98 @@ +module.exports = { + + 'google': { + auth_endpoint: 'https://accounts.google.com/o/oauth2/v2/auth', + token_endpoint: 'https://www.googleapis.com/oauth2/v4/token', + params: { access_type: 'offline' } + }, + + 'facebook': { + auth_endpoint: 'https://www.facebook.com/v3.2/dialog/oauth', + token_endpoint: 'https://graph.facebook.com/v3.2/oauth/access_token' + }, + + 'linkedin': { + auth_endpoint: 'https://www.linkedin.com/oauth/v2/authorization', + token_endpoint: 'https://www.linkedin.com/oauth/v2/accessToken' + }, + + 'github': { + auth_endpoint: 'https://github.com/login/oauth/authorize', + token_endpoint: 'https://github.com/login/oauth/access_token' + }, + + 'instagram': { + auth_endpoint: 'https://api.instagram.com/oauth/authorize/', + token_endpoint: 'https://api.instagram.com/oauth/access_token' + }, + + 'amazon': { + auth_endpoint: 'https://www.amazon.com/ap/oa', + token_endpoint: 'https://api.amazon.com/auth/o2/token' + }, + + 'dropbox': { + auth_endpoint: 'https://www.dropbox.com/oauth2/authorize', + token_endpoint: 'https://api.dropbox.com/oauth2/token', + scope_separator: ',' + }, + + 'foursquare': { + auth_endpoint: 'https://foursquare.com/oauth2/authenticate', + token_endpoint: 'https://foursquare.com/oauth2/access_token' + }, + + 'imgur': { + auth_endpoint: 'https://api.imgur.com/oauth2/authorize', + token_endpoint: 'https://api.imgur.com/oauth2/token' + }, + + 'wordpress': { + auth_endpoint: 'https://public-api.wordpress.com/oauth2/authorize', + token_endpoint: 'https://public-api.wordpress.com/oauth2/token' + }, + + 'spotify': { + auth_endpoint: 'https://accounts.spotify.com/authorize', + token_endpoint: 'https://accounts.spotify.com/api/token' + }, + + 'slack': { + auth_endpoint: 'https://slack.com/oauth/authorize', + token_endpoint: 'https://slack.com/api/oauth.access' + }, + + 'reddit': { + auth_endpoint: 'https://ssl.reddit.com/api/v1/authorize', + token_endpoint: 'https://ssl.reddit.com/api/v1/access_token', + scope_separator: ',' + }, + + 'twitch': { + auth_endpoint: 'https://api.twitch.tv/kraken/oauth2/authorize', + token_endpoint: 'https://api.twitch.tv/kraken/oauth2/token' + }, + + 'paypal': { + auth_endpoint: 'https://identity.x.com/xidentity/resources/authorize', + token_endpoint: 'https://identity.x.com/xidentity/oauthtokenservice' + }, + + 'pinterest': { + auth_endpoint: 'https://api.pinterest.com/oauth/', + token_endpoint: 'https://api.pinterest.com/v1/oauth/token', + scope_separator: ',' + }, + + 'stripe': { + auth_endpoint: 'https://connect.stripe.com/oauth/authorize', + token_endpoint: 'https://connect.stripe.com/oauth/token', + scope_separator: ',' + }, + + 'coinbase': { + auth_endpoint: 'https://www.coinbase.com/oauth/authorize', + token_endpoint: 'https://www.coinbase.com/oauth/token' + } + +}; \ No newline at end of file diff --git a/lib/server.js b/lib/server.js new file mode 100644 index 0000000..fdded76 --- /dev/null +++ b/lib/server.js @@ -0,0 +1,94 @@ +if (process.env.NODE_ENV !== 'production') { + require('dotenv').config(); +} + +process.on('uncaughtException', (e) => { + // prevent errors from killing the server and just log them + console.error(e); +}); + +const config = require('./setup/config'); +const debug = require('debug')('server-connect:server'); +const routes = require('./setup/routes'); +const sockets = require('./setup/sockets'); +const upload = require('./setup/upload'); +const cron = require('./setup/cron'); +const http = require('http'); +const express = require('express'); +const endmw = require('express-end'); +const cookieParser = require('cookie-parser'); +const session = require('./setup/session'); //require('express-session')(Object.assign({ secret: config.secret }, config.session)); +const cors = require('cors'); +const app = express(); + +app.set('trust proxy', true); +app.set('view engine', 'ejs'); +app.set('view options', { root: 'views', async: true }); + +app.disable('x-powered-by') + +if (config.compression) { + const compression = require('compression'); + app.use(compression()); +} + +if (config.abortOnDisconnect) { + app.use((req, res, next) => { + req.isDisconnected = false; + req.on('close', () => { + req.isDisconnected = true; + }); + + next(); + }); +} + +app.use(cors(config.cors)); +app.use(express.static('public', config.static)); +app.use(express.urlencoded({ extended: true })); +app.use(express.json({ + verify: (req, res, buf) => { + req.rawBody = buf.toString() + } +})); +app.use(cookieParser(config.secret)); +app.use(session); +app.use(endmw); + +upload(app); +routes(app); + +const server = http.createServer(app); +const io = sockets(server, session); + +// Make sockets global available +global.io = io; + +module.exports = { + server, app, io, + start: function(port) { + // We add the 404 and 500 routes as last + app.use((req, res) => { + res.status(404).json({ + status: '404', + message: `${req.url} not found.` + }); + }); + + app.use((err, req, res, next) => { + debug(`Got error? %O`, err); + res.status(500).json({ + status: '500', + code: config.debug ? err.code : undefined, + message: config.debug ? err.message || err : 'A server error occured, to see the error enable the DEBUG flag.', + stack: config.debug ? err.stack : undefined, + }); + }); + + cron.start(); + + server.listen(port || config.port, () => { + console.log(`App listening at http://localhost:${config.port}`); + }); + } +}; diff --git a/lib/setup/config.js b/lib/setup/config.js new file mode 100644 index 0000000..255484b --- /dev/null +++ b/lib/setup/config.js @@ -0,0 +1,107 @@ +const package = require('../../package.json'); +const fs = require('fs-extra'); +const debug = require('debug')('server-connect:setup:config'); +const { toSystemPath } = require('../core/path'); +const { mergeDeep } = require('../core/util'); +const Parser = require('../core/parser'); +const Scope = require('../core/scope'); + +const config = { + port: process.env.PORT || 3000, + debug: false, + secret: 'Need to be set', + tmpFolder: '/tmp', + abortOnDisconnect: false, + createApiRoutes: true, + compression: true, + redis: false, + cron: true, + static: { + index: false + }, + session: { + name: package.name + '.sid', + resave: false, + saveUninitialized: false, + store: { $type: 'memory', ttl: 86400000 } + }, + cors: { // see https://github.com/expressjs/cors + origin: false, + methods: 'GET,POST', + allowedHeaders: '*', + credentials: true + }, + globals: {}, + mail: {}, + auth: {}, + oauth: {}, + db: {}, + s3: {}, + jwt: {}, + stripe: {}, + env: {} +}; + +if (fs.existsSync('app/config/config.json')) { + mergeDeep(config, fs.readJSONSync('app/config/config.json')) +} + +if (fs.existsSync('app/config/user_config.json')) { + mergeDeep(config, fs.readJSONSync('app/config/user_config.json')); +} + +// folders are site relative +config.tmpFolder = toSystemPath(config.tmpFolder); + +if (config.env) { + for (let key in config.env) { + if (!Object.prototype.hasOwnProperty.call(process.env, key)) { + process.env[key] = config.env[key]; + } else if (config.debug) { + debug(`"${key}" is already defined in \`process.env\` and will not be overwritten`); + } + } +} + +Parser.parseValue(config, new Scope({ + $_ENV: process.env +})); + +// we change the cors config a bit, * will become true +// and we split string on comma for multiple origins +if (typeof config.cors?.origin == 'string') { + if (config.cors.origin === '*') { + config.cors.origin = true; + } else if (config.cors.origin.includes(',')) { + config.cors.origin = config.cors.origin.split(/\s*,\s*/); + } +} + +if (config.debug) { + require('debug').enable(typeof config.debug == 'string' ? config.debug : 'server-connect:*'); +} + +if (config.redis) { + const { createClient } = require('redis'); + const client = createClient(typeof config.redis == 'object' ? config.redis : { + url: config.redis === true ? 'redis://redis' : config.redis, + socket: { connectTimeout: 60000 }, + pingInterval: 10000, + }); + + client.on('error', err => console.error(err)); + client.on('connect', () => debug('Redis Connected')); + client.on('reconnecting', () => debug('Redis Reconnecting')); + client.on('ready', () => debug('Redis Ready')); + + client.connect().catch(err => { + // we don't want to crash the server if redis is not available + }); + + global.redisClient = client; + +} + +debug(config); + +module.exports = config; \ No newline at end of file diff --git a/lib/setup/cron.js b/lib/setup/cron.js new file mode 100644 index 0000000..f33797f --- /dev/null +++ b/lib/setup/cron.js @@ -0,0 +1,46 @@ +const fs = require('fs-extra'); +const { isEmpty } = require('./util'); +const config = require('./config'); +const debug = require('debug')('server-connect:cron'); + +exports.start = () => { + if (!config.cron || isEmpty('app/schedule')) return; + + debug('Start schedule'); + + processEntries('app/schedule'); +}; + +function processEntries(path) { + const schedule = require('node-schedule'); + const entries = fs.readdirSync(path, { withFileTypes: true }); + + for (let entry of entries) { + if (entry.isFile() && entry.name.endsWith('.json')) { + try { + const job = fs.readJSONSync(`${path}/${entry.name}`); + const rule = job.settings.options.rule; + + debug(`Adding schedule ${entry.name}`); + + if (rule == '@reboot') { + setImmediate(exec(job.exec)); + } else { + schedule.scheduleJob(rule, exec(job.exec)) + } + } catch (e) { + console.error(e); + } + } else if (entry.isDirectory()) { + processEntries(`${path}/${entry.name}`); + } + } +} + +function exec(action) { + return async () => { + const App = require('../core/app'); + const app = new App({ params: {}, session: {}, cookies: {}, signedCookies: {}, query: {}, headers: {} }); + return app.define(action, true); + } +} \ No newline at end of file diff --git a/lib/setup/database.js b/lib/setup/database.js new file mode 100644 index 0000000..b41f008 --- /dev/null +++ b/lib/setup/database.js @@ -0,0 +1,36 @@ +// allow Wappler to run queries on the server + +const crypto = require('crypto'); +const App = require("../core/app"); +const config = require('./config'); + +module.exports = function (app) { + app.post('/_db_', async (req, res) => { + const time = req.get('auth-time'); + const hash = req.get('auth-hash'); + if (!time || !hash || isNaN(time)) return res.status(400).json({ error: 'Auth headers missing' }); + + const diff = Math.abs(Date.now() - time) / 1000; + if (diff > 60) return res.status(400).json({ error: 'Auth time diff to high' }); + if (hash !== crypto.createHmac('sha256', config.secret).update(req.rawBody).digest('hex')) return res.status(400).json({ error: 'Auth hash invalid' }); + + const sc = new App(req, res); + const db = sc.getDbConnection(req.body.name); + + let results = []; + + try { + results = await db.raw(req.body.query); + } catch (error) { + return res.json({ error: error.sqlMessage }); + } + + if (db.client.config.client == 'mysql' || db.client.config.client == 'mysql2') { + results = results[0]; + } else if (db.client.config.client == 'postgres' || db.client.config.client == 'redshift') { + results = results.rows; + } + + res.json({ results }); + }); +} diff --git a/lib/setup/redis.js b/lib/setup/redis.js new file mode 100644 index 0000000..3dff4be --- /dev/null +++ b/lib/setup/redis.js @@ -0,0 +1,8 @@ +const config = require('./config'); + +if (config.redis) { + const redis = require('redis'); + global.redisClient = redis.createClient(config.redis === true ? 'redis://redis' : config.redis); +} + +module.exports = global.redisClient; \ No newline at end of file diff --git a/lib/setup/routes.js b/lib/setup/routes.js new file mode 100644 index 0000000..dd2eb45 --- /dev/null +++ b/lib/setup/routes.js @@ -0,0 +1,249 @@ +const fs = require('fs-extra'); +const debug = require('debug')('server-connect:setup:routes'); +const config = require('./config'); +const { map } = require('../core/async'); +const { posix, extname } = require('path'); +const { cache, serverConnect, templateView } = require('../core/middleware'); +const database = require('./database'); +const webhooks = require('./webhooks'); + +module.exports = async function (app) { + app.use((req, res, next) => { + req.fragment = (req.headers['accept'] || '*/*').includes('fragment'); + next(); + }); + + if (fs.existsSync('extensions/server_connect/routes')) { + const entries = fs.readdirSync('extensions/server_connect/routes', { withFileTypes: true }); + + for (let entry of entries) { + if (entry.isFile() && extname(entry.name) == '.js') { + let hook = require(`../../extensions/server_connect/routes/${entry.name}`); + if (hook.before) hook.before(app); + if (hook.handler) hook.handler(app); + debug(`Custom router ${entry.name} loaded`); + } + } + } + + if (config.createApiRoutes) { + fs.ensureDirSync('app/api'); + createApiRoutes('app/api'); + } + + if (fs.existsSync('app/config/routes.json')) { + const { routes, layouts } = fs.readJSONSync('app/config/routes.json'); + + parseRoutes(routes, null); + + function parseRoutes(routes, parent) { + for (let route of routes) { + if (!route.path) continue; + + createRoute(route, parent); + + if (Array.isArray(route.routes)) { + parseRoutes(route.routes, route); + } + } + } + + function createRoute({ auth, path, method, redirect, url, page, layout, exec, data, ttl, status, proxy }, parent) { + method = method || 'all'; + data = data || {}; + if (page) page = page.replace(/^\//, ''); + if (layout) layout = layout.replace(/^\//, ''); + if (parent && parent.path) path = parent.path + path; + + if (auth) { + app.use(path, (req, res, next) => { + if (typeof auth == 'string' && req.session && req.session[auth + 'Id']) { + next(); + } else if (typeof auth == 'object' && auth.user) { + const b64auth = (req.headers.authorization || '').split(' ')[1] || ''; + const [user, password] = Buffer.from(b64auth, 'base64').toString().split(':'); + if (user && password && user === auth.user && password === auth.password) { + next(); + } else { + res.set('WWW-Authenticate', 'Basic realm="401"'); + res.status(401).json({ error: 'Unauthorized' }); + } + } else { + res.status(401).json({ error: 'Unauthorized' }); + } + }); + } + + if (proxy) { + const httpProxy = require('http-proxy'); + const proxyServer = httpProxy.createProxyServer(proxy); + app.use(path, (req, res) => { + proxyServer.web(req, res); + }); + } else if (redirect) { + app.get(path, (req, res) => res.redirect(status == 302 ? 302 : 301, redirect)); + } else if (url) { + app[method](path, (req, res, next) => { + next(parent && !req.fragment ? 'route' : null); + }, (req, res) => { + res.sendFile(url, { root: 'public' }) + }); + + if (parent) { + createRoute({ + path, + method: parent.method, + redirect: parent.redirect, + url: parent.url, + page: parent.page, + layout: parent.layout, + exec: parent.exec, + data: parent.data + }); + } + } else if (page) { + if (exec) { + if (fs.existsSync(`app/${exec}.json`)) { + let json = fs.readJSONSync(`app/${exec}.json`); + + if (json.exec && json.exec.steps) { + json = json.exec.steps; + } else if (json.steps) { + json = json.steps; + } + + if (!Array.isArray(json)) { + json = [json]; + } + + + if (layout && layouts && layouts[layout]) { + if (layouts[layout].data) { + data = Object.assign({}, layouts[layout].data, data); + } + + if (layouts[layout].exec) { + if (fs.existsSync(`app/${layouts[layout].exec}.json`)) { + let _json = fs.readJSONSync(`app/${layouts[layout].exec}.json`); + + if (_json.exec && _json.exec.steps) { + _json = _json.exec.steps; + } else if (_json.steps) { + _json = _json.steps; + } + + if (!Array.isArray(_json)) { + _json = [_json]; + } + + json = _json.concat(json); + } else { + debug(`Route ${path} skipped, "app/${exec}.json" not found`); + return; + } + } + } + + app[method](path, (req, res, next) => { + next(parent && !req.fragment ? 'route' : null); + }, cache({ttl}), templateView(layout, page, data, json)); + } else { + debug(`Route ${path} skipped, "app/${exec}.json" not found`); + return; + } + } else { + let json = []; + + if (layout && layouts && layouts[layout]) { + if (layouts[layout].data) { + data = Object.assign({}, layouts[layout].data, data); + } + + if (layouts[layout].exec) { + if (fs.existsSync(`app/${layouts[layout].exec}.json`)) { + let _json = fs.readJSONSync(`app/${layouts[layout].exec}.json`); + + if (_json.exec && _json.exec.steps) { + _json = _json.exec.steps; + } else if (_json.steps) { + _json = _json.steps; + } + + if (!Array.isArray(_json)) { + _json = [_json]; + } + + json = _json.concat(json); + } else { + debug(`Route ${path} skipped, "app/${exec}.json" not found`); + return; + } + } + } + + app[method](path, (req, res, next) => { + next(parent && !req.fragment ? 'route' : null); + }, cache({ttl}), templateView(layout, page, data, json)); + } + + if (parent) { + createRoute({ + path, + method: parent.method, + redirect: parent.redirect, + url: parent.url, + page: parent.page, + layout: parent.layout, + exec: parent.exec, + data: parent.data + }); + } + } else if (exec) { + if (fs.existsSync(`app/${exec}.json`)) { + let json = fs.readJSONSync(`app/${exec}.json`); + + app[method](path, cache({ttl}), serverConnect(json)); + + return; + } + } + } + } + + database(app); + webhooks(app); + + if (fs.existsSync('extensions/server_connect/routes')) { + const entries = fs.readdirSync('extensions/server_connect/routes', { withFileTypes: true }); + + for (let entry of entries) { + if (entry.isFile() && extname(entry.name) == '.js') { + let hook = require(`../../extensions/server_connect/routes/${entry.name}`); + if (hook.after) hook.after(app); + debug(`Custom router ${entry.name} loaded`); + } + } + } + + function createApiRoutes(dir) { + const entries = fs.readdirSync(dir, { withFileTypes: true }); + + return map(entries, async (entry) => { + let path = posix.join(dir, entry.name); + + if (entry.isFile() && extname(path) == '.json') { + let json = fs.readJSONSync(path); + let routePath = path.replace(/^app/i, '').replace(/.json$/, '(.json)?'); + let ttl = (json.settings && json.settings.options && json.settings.options.ttl) ? json.settings.options.ttl : 0; + + app.all(routePath, cache({ttl}), serverConnect(json)); + + debug(`Api route ${routePath} created`); + } + + if (entry.isDirectory()) { + return createApiRoutes(path); + } + }); + } +}; diff --git a/lib/setup/session.js b/lib/setup/session.js new file mode 100644 index 0000000..f44aac2 --- /dev/null +++ b/lib/setup/session.js @@ -0,0 +1,87 @@ +const session = require('express-session'); //(Object.assign({ secret: config.secret }, config.session)); +const debug = require('debug')('server-connect:setup:session'); +const Parser = require('../core/parser'); +const Scope = require('../core/scope'); +const db = require('../core/db'); +const { toSystemPath } = require('../core/path'); +const config = require('./config'); +const options = config.session; + +if (!options.secret) { + options.secret = config.secret; +} + +debug('init session store %o', options.store); + +if (options.store.$type == 'redis') { // https://www.npmjs.com/package/connect-redis + const RedisStore = require('connect-redis').default; + options.store = new RedisStore(Object.assign({ + client: global.redisClient + }, options.store)); +} else if (options.store.$type == 'file') { // https://www.npmjs.com/package/session-file-store + const FileStore = require('session-file-store')(session); + options.store = new FileStore(options.store); +} else if (options.store.$type == 'database') { // https://www.npmjs.com/package/connect-session-knex + const KnexStore = require('connect-session-knex')(session); + + if (typeof options.store.knex == 'string') { + if (!global.db) global.db = {}; + + if (!global.db[options.store.knex]) { + const fs = require('fs-extra'); + const action = fs.readJSONSync(`app/modules/connections/${options.store.knex}.json`); + const knex_options = Parser.parseValue(action.options, new Scope({ $_ENV: process.env })); + + if (knex_options.connection && knex_options.connection.filename) { + knex_options.connection.filename = toSystemPath(knex_options.connection.filename); + } + + if (knex_options.connection && knex_options.connection.ssl) { + if (knex_options.connection.ssl.key) { + knex_options.connection.ssl.key = fs.readFileSync(toSystemPath(knex_options.connection.ssl.key)); + } + + if (knex_options.connection.ssl.ca) { + knex_options.connection.ssl.ca = fs.readFileSync(toSystemPath(knex_options.connection.ssl.ca)); + } + + if (knex_options.connection.ssl.cert) { + knex_options.connection.ssl.cert = fs.readFileSync(toSystemPath(knex_options.connection.ssl.cert)); + } + } + + knex_options.useNullAsDefault = true; + + knex_options.postProcessResponse = function(result) { + if (Array.isArray(result)) { + return result.map(row => { + for (column in row) { + if (row[column] && row[column].toJSON) { + row[column] = row[column].toJSON(); + } + } + return row; + }); + } else { + for (column in result) { + if (result[column] && result[column].toJSON) { + result[column] = result[column].toJSON(); + } + } + return result; + } + }; + + global.db[options.store.knex] = db(knex_options); + } + + options.store.knex = global.db[options.store.knex]; + } + + options.store = new KnexStore(options.store); +} else { + const MemoryStore = require('../core/memoryStore')(session); + options.store = new MemoryStore(options.store); +} + +module.exports = session(options); \ No newline at end of file diff --git a/lib/setup/sockets.js b/lib/setup/sockets.js new file mode 100644 index 0000000..211db52 --- /dev/null +++ b/lib/setup/sockets.js @@ -0,0 +1,198 @@ +const fs = require('fs-extra'); +const { basename, extname } = require('path'); +const debug = require('debug')('server-connect:sockets'); +const { isEmpty } = require('./util'); +const config = require('./config'); +const cookieParser = require('cookie-parser'); +const { promisify } = require('util'); + +module.exports = function (server, appSession) { + //if (isEmpty('app/sockets')) return null; + + const io = require('socket.io')(); + + if (global.redisClient) { + const { createAdapter } = require('@socket.io/redis-adapter'); + const pubClient = global.redisClient.duplicate(); + const subClient = global.redisClient.duplicate(); + + Promise.all([pubClient.connect(), subClient.connect()]).then(() => { + io.adapter(createAdapter(pubClient, subClient)); + }); + } + + // user hooks + if (fs.existsSync('extensions/server_connect/sockets')) { + const entries = fs.readdirSync('extensions/server_connect/sockets', { withFileTypes: true }); + + for (let entry of entries) { + if (entry.isFile() && extname(entry.name) == '.js') { + const hook = require(`../../extensions/server_connect/sockets/${entry.name}`); + if (hook.handler) hook.handler(io); + debug(`Custom sockets hook ${entry.name} loaded`); + } + } + } + + // create socket connections for api endpoints + if (fs.existsSync('app/api')) { + + io.of('/api').on('connection', async (socket) => { + socket.onAny(async (event, params, cb) => { + try { + if (typeof cb == 'function' && global.redisClient && global.redisClient.isReady) { + const cached = await global.redisClient.get('ws:' + event + ':' + JSON.stringify(params)); + if (cached) return cb(JSON.parse(cached)); + } + + const req = Object.assign({}, socket.handshake); + const res = { + statusCode: 200, + getHeader: () => { }, + setHeader: () => { }, + sendStatus: (statusCode) => { res.statusCode = statusCode; }, + write: () => { }, + end: () => { } + }; + + cookieParser(config.secret)(req, res, () => { + appSession(req, res, async () => { + const App = require('../core/app'); + const app = new App(req, res); + const action = await fs.readJSON(`app/api/${event}.json`); + app.set('$_PARAM', params); + app.set('$_GET', params); // fake query params + app.socket = socket; + await app.define(action, true); + if (typeof cb == 'function') { + cb({ + status: res.statusCode, + data: res.statusCode == 200 ? app.data : null + }); + + if (global.redisClient && global.redisClient.isReady) { + let ttl = (action.settings && action.settings.options && action.settings.options.ttl) ? action.settings.options.ttl : null; + + if (ttl && res.statusCode < 400) { // only cache valid response, not error response + global.redisClient.setEx('ws:' + event + ':' + JSON.stringify(params), ttl, JSON.stringify({ + status: res.statusCode, + data: res.statusCode == 200 ? app.data : null + })); + } + } + } + }); + }); + } catch (e) { + debug(`ERROR: ${e.message}`); + console.error(e); + } + }); + }); + } + + if (fs.existsSync('app/sockets')) { + parseSockets(); + } + + function parseSockets(namespace = '') { + const entries = fs.readdirSync('app/sockets' + namespace, { withFileTypes: true }); + + io.of(namespace || '/').on('connection', async (socket) => { + if (fs.existsSync(`app/sockets${namespace}/connect.json`)) { + try { + const req = Object.assign({}, socket.handshake); + const res = { + statusCode: 200, + getHeader: () => { }, + setHeader: () => { }, + sendStatus: (statusCode) => { res.statusCode = statusCode; }, + write: () => { }, + end: () => { } + }; + + cookieParser(config.secret)(req, res, () => { + appSession(req, res, async () => { + const App = require('../core/app'); + const app = new App(req, res); + const action = await fs.readJSON(`app/sockets${namespace}/connect.json`); + app.socket = socket; + await app.define(action, true); + }); + }); + } catch (e) { + debug(`ERROR: ${e.message}`); + console.error(e); + } + } + + if (fs.existsSync(`app/sockets${namespace}/disconnect.json`)) { + socket.on('disconnect', async (event) => { + try { + const req = Object.assign({}, socket.handshake); + const res = { + statusCode: 200, + getHeader: () => { }, + setHeader: () => { }, + sendStatus: (statusCode) => { res.statusCode = statusCode; }, + write: () => { }, + end: () => { } + }; + + cookieParser(config.secret)(req, res, () => { + appSession(req, res, async () => { + const App = require('../core/app'); + const app = new App(req, res); + const action = await fs.readJSON(`app/sockets${namespace}/disconnect.json`); + app.socket = socket; + await app.define(action, true); + }); + }); + } catch (e) { + debug(`ERROR: ${e.message}`); + console.error(e); + } + }); + } + + socket.onAny(async (event, params, cb) => { + try { + const req = Object.assign({}, socket.handshake); + const res = { + statusCode: 200, + getHeader: () => { }, + setHeader: () => { }, + sendStatus: (statusCode) => { res.statusCode = statusCode; }, + write: () => { }, + end: () => { } + }; + + cookieParser(config.secret)(req, res, () => { + appSession(req, res, async () => { + const App = require('../core/app'); + const app = new App(req, res); + const action = await fs.readJSON(`app/sockets${namespace}/${event}.json`); + app.set('$_PARAM', params); + app.socket = socket; + await app.define(action, true); + if (typeof cb == 'function') cb(app.data); + }); + }); + } catch (e) { + debug(`ERROR: ${e.message}`); + console.error(e); + } + }); + }); + + for (let entry of entries) { + if (entry.isDirectory()) { + parseSockets(namespace + '/' + entry.name); + } + } + } + + io.attach(server); + + return io; +}; \ No newline at end of file diff --git a/lib/setup/upload.js b/lib/setup/upload.js new file mode 100644 index 0000000..b412b9d --- /dev/null +++ b/lib/setup/upload.js @@ -0,0 +1,61 @@ +const config = require('./config'); +const debug = require('debug')('server-connect:setup:upload'); +const fs = require('fs-extra'); +const qs = require('qs'); + +module.exports = function(app) { + const fileupload = require('express-fileupload'); + const isEligibleRequest = require('express-fileupload/lib/isEligibleRequest'); + + // Make sure tmp folder exists and make it empty + fs.ensureDirSync(config.tmpFolder); + fs.emptyDirSync(config.tmpFolder); + + // Always use tmp folder + app.use(fileupload(Object.assign({}, config.fileupload, { + useTempFiles: true, + tempFileDir: config.tmpFolder + }))); + + app.use((req, res, next) => { + if (!isEligibleRequest(req)) { + return next(); + } + + let encoded = qs.stringify(req.body); + if (req.files) { + for (let field in req.files) { + encoded += '&' + field + '=' + field; + } + } + + req.body = qs.parse(encoded, { + arrayLimit: 1000 + }); + + // Cleanup + res.once('close', () => { + if (req.files) { + for (let field in req.files) { + let file = req.files[field]; + + if (Array.isArray(file)) { + file.forEach((file) => { + if (file.tempFilePath && fs.existsSync(file.tempFilePath)) { + debug('delete %s', file.tempFilePath); + fs.unlink(file.tempFilePath); + } + }); + } else if (file.tempFilePath && fs.existsSync(file.tempFilePath)) { + debug('delete %s', file.tempFilePath); + fs.unlink(file.tempFilePath); + } + } + } + }); + + next(); + }); + + debug('Upload middleware configured.'); +} \ No newline at end of file diff --git a/lib/setup/util.js b/lib/setup/util.js new file mode 100644 index 0000000..c14f51c --- /dev/null +++ b/lib/setup/util.js @@ -0,0 +1,12 @@ +const fs = require('fs-extra'); + +exports.isEmpty = (path) => { + try { + let stat = fs.statSync(path); + if (!stat.isDirectory()) return true; + let items = fs.readdirSync(path); + return !items || !items.length; + } catch (e) { + return true; + } +} \ No newline at end of file diff --git a/lib/setup/webhooks.js b/lib/setup/webhooks.js new file mode 100644 index 0000000..58fab9e --- /dev/null +++ b/lib/setup/webhooks.js @@ -0,0 +1,22 @@ +const fs = require('fs-extra'); + +module.exports = function(app) { + app.all('/webhooks/:name', (req, res, next) => { + const name = req.params.name; + + if (!/^[a-zA-Z0-9-_]+$/.test(name)) { + res.status(400).json({error: `Invalid webhook name.`}); + } else if (fs.existsSync(`lib/webhooks/${name}.js`)) { + const webhook = require(`../webhooks/${name}`); + if (webhook.handler) { + webhook.handler(req, res, next); + } else { + res.status(400).json({error: `Webhook ${name} has no handler.`}); + } + } else { + const webhook = require('../core/webhook'); + const handler = webhook.createHandler(name); + handler(req, res, next); + } + }); +}; \ No newline at end of file diff --git a/lib/validator/core.js b/lib/validator/core.js new file mode 100644 index 0000000..5de27a9 --- /dev/null +++ b/lib/validator/core.js @@ -0,0 +1,243 @@ +const isValidString = (s) => typeof s == 'string' && s.length > 0; +const getLength = (s) => s && s.length || 0;; +const testRegexp = (re) => (v) => !isValidString(v) || re.test(v); +const reEmail = /^(?!\.)((?!.*\.{2})[a-zA-Z0-9\u0080-\u00FF\u0100-\u017F\u0180-\u024F\u0250-\u02AF\u0300-\u036F\u0370-\u03FF\u0400-\u04FF\u0500-\u052F\u0530-\u058F\u0590-\u05FF\u0600-\u06FF\u0700-\u074F\u0750-\u077F\u0780-\u07BF\u07C0-\u07FF\u0900-\u097F\u0980-\u09FF\u0A00-\u0A7F\u0A80-\u0AFF\u0B00-\u0B7F\u0B80-\u0BFF\u0C00-\u0C7F\u0C80-\u0CFF\u0D00-\u0D7F\u0D80-\u0DFF\u0E00-\u0E7F\u0E80-\u0EFF\u0F00-\u0FFF\u1000-\u109F\u10A0-\u10FF\u1100-\u11FF\u1200-\u137F\u1380-\u139F\u13A0-\u13FF\u1400-\u167F\u1680-\u169F\u16A0-\u16FF\u1700-\u171F\u1720-\u173F\u1740-\u175F\u1760-\u177F\u1780-\u17FF\u1800-\u18AF\u1900-\u194F\u1950-\u197F\u1980-\u19DF\u19E0-\u19FF\u1A00-\u1A1F\u1B00-\u1B7F\u1D00-\u1D7F\u1D80-\u1DBF\u1DC0-\u1DFF\u1E00-\u1EFF\u1F00-\u1FFFu20D0-\u20FF\u2100-\u214F\u2C00-\u2C5F\u2C60-\u2C7F\u2C80-\u2CFF\u2D00-\u2D2F\u2D30-\u2D7F\u2D80-\u2DDF\u2F00-\u2FDF\u2FF0-\u2FFF\u3040-\u309F\u30A0-\u30FF\u3100-\u312F\u3130-\u318F\u3190-\u319F\u31C0-\u31EF\u31F0-\u31FF\u3200-\u32FF\u3300-\u33FF\u3400-\u4DBF\u4DC0-\u4DFF\u4E00-\u9FFF\uA000-\uA48F\uA490-\uA4CF\uA700-\uA71F\uA800-\uA82F\uA840-\uA87F\uAC00-\uD7AF\uF900-\uFAFF\.!#$%&'*+-/=?^_`{|}~\-\d]+)@(?!\.)([a-zA-Z0-9\u0080-\u00FF\u0100-\u017F\u0180-\u024F\u0250-\u02AF\u0300-\u036F\u0370-\u03FF\u0400-\u04FF\u0500-\u052F\u0530-\u058F\u0590-\u05FF\u0600-\u06FF\u0700-\u074F\u0750-\u077F\u0780-\u07BF\u07C0-\u07FF\u0900-\u097F\u0980-\u09FF\u0A00-\u0A7F\u0A80-\u0AFF\u0B00-\u0B7F\u0B80-\u0BFF\u0C00-\u0C7F\u0C80-\u0CFF\u0D00-\u0D7F\u0D80-\u0DFF\u0E00-\u0E7F\u0E80-\u0EFF\u0F00-\u0FFF\u1000-\u109F\u10A0-\u10FF\u1100-\u11FF\u1200-\u137F\u1380-\u139F\u13A0-\u13FF\u1400-\u167F\u1680-\u169F\u16A0-\u16FF\u1700-\u171F\u1720-\u173F\u1740-\u175F\u1760-\u177F\u1780-\u17FF\u1800-\u18AF\u1900-\u194F\u1950-\u197F\u1980-\u19DF\u19E0-\u19FF\u1A00-\u1A1F\u1B00-\u1B7F\u1D00-\u1D7F\u1D80-\u1DBF\u1DC0-\u1DFF\u1E00-\u1EFF\u1F00-\u1FFF\u20D0-\u20FF\u2100-\u214F\u2C00-\u2C5F\u2C60-\u2C7F\u2C80-\u2CFF\u2D00-\u2D2F\u2D30-\u2D7F\u2D80-\u2DDF\u2F00-\u2FDF\u2FF0-\u2FFF\u3040-\u309F\u30A0-\u30FF\u3100-\u312F\u3130-\u318F\u3190-\u319F\u31C0-\u31EF\u31F0-\u31FF\u3200-\u32FF\u3300-\u33FF\u3400-\u4DBF\u4DC0-\u4DFF\u4E00-\u9FFF\uA000-\uA48F\uA490-\uA4CF\uA700-\uA71F\uA800-\uA82F\uA840-\uA87F\uAC00-\uD7AF\uF900-\uFAFF\-\.\d]+)((\.([a-zA-Z\u0080-\u00FF\u0100-\u017F\u0180-\u024F\u0250-\u02AF\u0300-\u036F\u0370-\u03FF\u0400-\u04FF\u0500-\u052F\u0530-\u058F\u0590-\u05FF\u0600-\u06FF\u0700-\u074F\u0750-\u077F\u0780-\u07BF\u07C0-\u07FF\u0900-\u097F\u0980-\u09FF\u0A00-\u0A7F\u0A80-\u0AFF\u0B00-\u0B7F\u0B80-\u0BFF\u0C00-\u0C7F\u0C80-\u0CFF\u0D00-\u0D7F\u0D80-\u0DFF\u0E00-\u0E7F\u0E80-\u0EFF\u0F00-\u0FFF\u1000-\u109F\u10A0-\u10FF\u1100-\u11FF\u1200-\u137F\u1380-\u139F\u13A0-\u13FF\u1400-\u167F\u1680-\u169F\u16A0-\u16FF\u1700-\u171F\u1720-\u173F\u1740-\u175F\u1760-\u177F\u1780-\u17FF\u1800-\u18AF\u1900-\u194F\u1950-\u197F\u1980-\u19DF\u19E0-\u19FF\u1A00-\u1A1F\u1B00-\u1B7F\u1D00-\u1D7F\u1D80-\u1DBF\u1DC0-\u1DFF\u1E00-\u1EFF\u1F00-\u1FFF\u20D0-\u20FF\u2100-\u214F\u2C00-\u2C5F\u2C60-\u2C7F\u2C80-\u2CFF\u2D00-\u2D2F\u2D30-\u2D7F\u2D80-\u2DDF\u2F00-\u2FDF\u2FF0-\u2FFF\u3040-\u309F\u30A0-\u30FF\u3100-\u312F\u3130-\u318F\u3190-\u319F\u31C0-\u31EF\u31F0-\u31FF\u3200-\u32FF\u3300-\u33FF\u3400-\u4DBF\u4DC0-\u4DFF\u4E00-\u9FFF\uA000-\uA48F\uA490-\uA4CF\uA700-\uA71F\uA800-\uA82F\uA840-\uA87F\uAC00-\uD7AF\uF900-\uFAFF]){2,63})+)$/i; +const reUrl = /^(?:(?:(?:https?|ftp):)?\/\/)(?:\S+(?::\S*)?@)?(?:(?!(?:10|127)(?:\.\d{1,3}){3})(?!(?:169\.254|192\.168)(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)(?:\.(?:[a-z\u00a1-\uffff0-9]-*)*[a-z\u00a1-\uffff0-9]+)*(?:\.(?:[a-z\u00a1-\uffff]{2,})).?)(?::\d{2,5})?(?:[/?#]\S*)?$/i; +const reDateTime = /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])[T ]([01][0-9]|2[0-4]):[0-5][0-9](:([0-5][0-9]|60))?Z?$/; +const reDate = /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/; +const reTime = /^([01][0-9]|2[0-4]):[0-5][0-9](:([0-5][0-9]|60))?$/; +const reMonth = /^\d{4}-(0[1-9]|1[012])$/; +const reWeek = /^\d{4}-W(0[1-9]|[1-4][0-9]|5[0-3])$/; +const reColor = /^#[a-fA-F0-9]{6}$/; +const reNumber = /^(?:-?\d+|-?\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/; +const reDigits = /^\d+$/; +const reAlphanumeric = /^\w+$/; +const reBic = /^([A-Z]{6}[A-Z2-9][A-NP-Z1-2])(X{3}|[A-WY-Z0-9][A-Z0-9]{2})?$/; +const reVat = /^((AT)?U[0-9]{8}|(BE)?0[0-9]{9}|(BG)?[0-9]{9,10}|(CY)?[0-9]{8}L|(CZ)?[0-9]{8,10}|(DE)?[0-9]{9}|(DK)?[0-9]{8}|(EE)?[0-9]{9}|(EL|GR)?[0-9]{9}|(ES)?[0-9A-Z][0-9]{7}[0-9A-Z]|(FI)?[0-9]{8}|(FR)?[0-9A-Z]{2}[0-9]{9}|(GB)?([0-9]{9}([0-9]{3})?|[A-Z]{2}[0-9]{3})|(HU)?[0-9]{8}|(IE)?[0-9]S[0-9]{5}L|(IT)?[0-9]{11}|(LT)?([0-9]{9}|[0-9]{12})|(LU)?[0-9]{8}|(LV)?[0-9]{11}|(MT)?[0-9]{8}|(NL)?[0-9]{9}B[0-9]{2}|(PL)?[0-9]{10}|(PT)?[0-9]{9}|(RO)?[0-9]{2,10}|(SE)?[0-9]{12}|(SI)?[0-9]{8}|(SK)?[0-9]{10})$/; +const reInteger = /^-?\d+$/; +const reIpv4 = /^(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)$/; +const reIpv6 = /^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$/; +const reLettersonly = /^[a-z]+$/i; +const reUnicodelettersonly = /^[A-Za-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B4\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FD5\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AD\uA7B0-\uA7B7\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]+$/; +const reLetterswithbasicpunc = /^[a-z\-.,()'"\s]+$/; +const reNowhitespace = /^\S+$/; +const ibanCountryPatterns = { + "AL": "\\d{8}[\\dA-Z]{16}", + "AD": "\\d{8}[\\dA-Z]{12}", + "AT": "\\d{16}", + "AZ": "[\\dA-Z]{4}\\d{20}", + "BE": "\\d{12}", + "BH": "[A-Z]{4}[\\dA-Z]{14}", + "BA": "\\d{16}", + "BR": "\\d{23}[A-Z][\\dA-Z]", + "BG": "[A-Z]{4}\\d{6}[\\dA-Z]{8}", + "CR": "\\d{17}", + "HR": "\\d{17}", + "CY": "\\d{8}[\\dA-Z]{16}", + "CZ": "\\d{20}", + "DK": "\\d{14}", + "DO": "[A-Z]{4}\\d{20}", + "EE": "\\d{16}", + "FO": "\\d{14}", + "FI": "\\d{14}", + "FR": "\\d{10}[\\dA-Z]{11}\\d{2}", + "GE": "[\\dA-Z]{2}\\d{16}", + "DE": "\\d{18}", + "GI": "[A-Z]{4}[\\dA-Z]{15}", + "GR": "\\d{7}[\\dA-Z]{16}", + "GL": "\\d{14}", + "GT": "[\\dA-Z]{4}[\\dA-Z]{20}", + "HU": "\\d{24}", + "IS": "\\d{22}", + "IE": "[\\dA-Z]{4}\\d{14}", + "IL": "\\d{19}", + "IT": "[A-Z]\\d{10}[\\dA-Z]{12}", + "KZ": "\\d{3}[\\dA-Z]{13}", + "KW": "[A-Z]{4}[\\dA-Z]{22}", + "LV": "[A-Z]{4}[\\dA-Z]{13}", + "LB": "\\d{4}[\\dA-Z]{20}", + "LI": "\\d{5}[\\dA-Z]{12}", + "LT": "\\d{16}", + "LU": "\\d{3}[\\dA-Z]{13}", + "MK": "\\d{3}[\\dA-Z]{10}\\d{2}", + "MT": "[A-Z]{4}\\d{5}[\\dA-Z]{18}", + "MR": "\\d{23}", + "MU": "[A-Z]{4}\\d{19}[A-Z]{3}", + "MC": "\\d{10}[\\dA-Z]{11}\\d{2}", + "MD": "[\\dA-Z]{2}\\d{18}", + "ME": "\\d{18}", + "NL": "[A-Z]{4}\\d{10}", + "NO": "\\d{11}", + "PK": "[\\dA-Z]{4}\\d{16}", + "PS": "[\\dA-Z]{4}\\d{21}", + "PL": "\\d{24}", + "PT": "\\d{21}", + "RO": "[A-Z]{4}[\\dA-Z]{16}", + "SM": "[A-Z]\\d{10}[\\dA-Z]{12}", + "SA": "\\d{2}[\\dA-Z]{18}", + "RS": "\\d{18}", + "SK": "\\d{20}", + "SI": "\\d{15}", + "ES": "\\d{20}", + "SE": "\\d{20}", + "CH": "\\d{5}[\\dA-Z]{12}", + "TN": "\\d{20}", + "TR": "\\d{5}[\\dA-Z]{17}", + "AE": "\\d{3}\\d{16}", + "GB": "[A-Z]{4}\\d{14}", + "VG": "[\\dA-Z]{4}\\d{16}" +}; + +module.exports = { + + required: function(value) { + return value && value.length > 0; + }, + + email: testRegexp(reEmail), + url: testRegexp(reUrl), + datetime: testRegexp(reDateTime), + date: testRegexp(reDate), + time: testRegexp(reTime), + month: testRegexp(reMonth), + week: testRegexp(reWeek), + color: testRegexp(reColor), + number: testRegexp(reNumber), + digits: testRegexp(reDigits), + alphanumeric: testRegexp(reAlphanumeric), + bic: testRegexp(reBic), + vat: testRegexp(reVat), + integer: testRegexp(reInteger), + ipv4: testRegexp(reIpv4), + ipv6: testRegexp(reIpv6), + lettersonly: testRegexp(reLettersonly), + unicodelettersonly: testRegexp(reUnicodelettersonly), + letterswithbasicpunc: testRegexp(reLetterswithbasicpunc), + nowhitespace: testRegexp(reNowhitespace), + + pattern: function(value, param) { + const re = new RegExp(`^(?:${param})$`); + return !isValidString(value) || re.test(value); + }, + + creditcard: function(value) { + if (!isValidString(value)) { + return true; + } + + if (/[^0-9 \-]+/.test(value)) { + return false; + } + + value = value.replace(/\D/g, ''); + + if (value.length < 13 || value.length > 19) { + return false; + } + + let check = 0, digit = 0, even = false; + for (let i = value.length - 1; i >= 0; i--) { + digit = parseInt(value.charAt(i), 10); + if (even && (digit *= 2) > 9) { + digit -= 9; + } + check += digit; + even = !even; + } + + return (check % 10) === 0; + }, + + iban: function(value) { + if (!isValidString(value)) { + return true; + } + + const iban = value.replace(/ /g, '').toUpperCase(); + const country = iban.substr(0, 2); + const pattern = ibanCountryPatterns[country]; + + if (typeof patter !== 'undefined') { + const re = new RegExp(`^[A-Z]{2}\\d{2}${pattern}$`); + if (!re.test(iban)) { + return false; + } + } + + let leadingZeroes = true, digits = '', rest = ''; + + const lookup = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; + const check = iban.substr(4) + iban.substr(0, 4); + + for (let i = 0; i < check.length; i++) { + const ch = check.charAt(i); + + if (ch !== '0') { + leadingZeroes = false; + } + + if (!leadingZeroes) { + digits += lookup.indexOf(ch); + } + } + + for (let i = 0; i < digits.length; i++) { + const ch = digits.charAt(i); + const op = rest + ch; + rest = op % 97; + } + + return rest === 1; + }, + + minlength: function(value, param) { + const length = getLength(value); + return length == 0 || length >= param; + }, + + maxlength: function(value, param) { + const length = getLength(value); + return length == 0 || length <= param; + }, + + rangelength: function(value, param) { + const length = getLength(value); + return length == 0 || (length >= param['0'] && length <= param['1']); + }, + + minitems: function(value, param) { + const length = getLength(value); + return length == 0 || (Array.isArray(value) && length >= param); + }, + + maxitems: function(value, param) { + const length = getLength(value); + return length == 0 || (Array.isArray(value) && length <= param); + }, + + rangeitems: function(value, param) { + const length = getLength(value); + return length == 0 || (Array.isArray(value) && length >= param['0'] && length <= param['1']); + }, + + min: function(value, param) { + return value != null && value != '' && Number(value) >= Number(param); + }, + + max: function(value, param) { + return value != null && value != '' && Number(value) <= Number(param); + }, + + range: function(value, param) { + return value != null && value != '' && Number(value) >= Number(param['0']) && Number(value) <= Number(param['1']); + }, + + equalTo: function(value, param) { + return this.parse(`{{ $_POST.${param.replace(/\[([^\]]+)\]/g, '.$1')} }}`) == value; + }, + + notEqualTo: function(value, param) { + return this.parse(`{{ $_POST.${param.replace(/\[([^\]]+)\]/g, '.$1')} }}`) != value; + }, + +}; \ No newline at end of file diff --git a/lib/validator/db.js b/lib/validator/db.js new file mode 100644 index 0000000..b7ef470 --- /dev/null +++ b/lib/validator/db.js @@ -0,0 +1,21 @@ +module.exports = { + + exists: async function(value, options) { + if (!value) return true; + + const db = this.getDbConnection(options.connection); + const results = await db.from(options.table).where(options.column, value).limit(1); + + return results.length > 0; + }, + + notexists: async function(value, options) { + if (!value) return true; + + const db = this.getDbConnection(options.connection); + const results = await db.from(options.table).where(options.column, value).limit(1); + + return results.length == 0; + } + +}; \ No newline at end of file diff --git a/lib/validator/index.js b/lib/validator/index.js new file mode 100644 index 0000000..e1c7b96 --- /dev/null +++ b/lib/validator/index.js @@ -0,0 +1,120 @@ +module.exports = { + + async init(app, meta) { + const errors = {}; + + if (meta['$_GET'] && Array.isArray(meta['$_GET'])) { + await this.validateFields(app, meta['$_GET'], app.scope.data['$_GET'], errors); + } + + if (meta['$_POST'] && Array.isArray(meta['$_POST'])) { + await this.validateFields(app, meta['$_POST'], app.scope.data['$_POST'], errors); + } + + if (Object.keys(errors).length) { + app.res.status(400).json(errors); + } + }, + + async validateData(app, data, noError) { + const errors = {}; + + if (Array.isArray(data)) { + for (let item of data) { + for (let rule in item.rules) { + const options = item.rules[rule]; + + rule = this.getRule(rule); + + if (!await this.validateRule(app, rule, item.value, options)) { + const t = item.fieldName ? 'form' : 'data'; + errors[t] = errors[t] || {}; + errors[t][item.fieldName || item.name] = this.errorMessage(rule, options); + } + } + } + } + + if (Object.keys(errors).length) { + if (noError) return false; + app.res.status(400).json(errors); + } + + return true; + }, + + async validateFields(app, fields, parent, errors, fieldname) { + if (parent == null) return; + + for (let field of fields) { + let value = parent[field.name]; + let curFieldname = fieldname ? `${fieldname}[${field.name}]` : field.name; + + if (field.type == 'array' && value == null) { + value = []; + } + + if (field.options && field.options.rules) { + await this.validateField(app, field, value, errors, curFieldname); + } + + if (field.type == 'object' && field.sub) { + await this.validateFields(app, field.sub, value, errors, curFieldname); + } + + if (field.type == 'array' && field.sub && field.sub[0] && field.sub[0].sub) { + if (Array.isArray(value) && value.length) { + for (let i = 0; i < value.length; i++) { + await this.validateFields(app, field.sub[0].sub, value[i], errors, `${curFieldname}[${i}]`); + } + } else { + await this.validateFields(app, field.sub[0].sub, null, errors, `${curFieldname}[0]`); + } + } + } + }, + + async validateField(app, field, value, errors, fieldname) { + for (let rule in field.options.rules) { + const options = field.options.rules[rule]; + + rule = this.getRule(rule); + + if (!await this.validateRule(app, rule, value, options)) { + const t = field.fieldName ? 'form' : 'data'; + errors[t] = errors[t] || {}; + errors[t][fieldname] = this.errorMessage(rule, options); + } + } + }, + + async validateRule(app, rule, value, options = {}) { + const module = require(`./${rule.module}`); + return module[rule.method].call(app, value, options.param); + }, + + errorMessage(rule, options = {}) { + let message = options.message; + + if (!message) { + const { validator: messages } = require('../locale/en-US'); + message = messages[rule.module][rule.method]; + } + + if (typeof options.param != 'object') { + options.param = { '0': options.param }; + } + + return message.replace(/{([^}]+)}/g, (m, i) => options.param[i]); + }, + + getRule(rule) { + const colon = rule.indexOf(':'); + + return { + module: colon > 0 ? rule.substr(0, colon) : 'core', + method: colon > 0 ? rule.substr(colon + 1) : rule + }; + } + +}; \ No newline at end of file diff --git a/lib/validator/unicode.js b/lib/validator/unicode.js new file mode 100644 index 0000000..61ed95c --- /dev/null +++ b/lib/validator/unicode.js @@ -0,0 +1,466 @@ +const unicode = { + 'L': { + bmp: 'A-Za-z\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B4\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16F1-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FD5\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788\uA78B-\uA7AD\uA7B0-\uA7B7\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA8FD\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB65\uAB70-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC', + astral: '\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD803[\uDC00-\uDC48\uDC80-\uDCB2\uDCC0-\uDCF2]|\uD83A[\uDC00-\uDCC4]|\uD801[\uDC00-\uDC9D\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF30-\uDF40\uDF42-\uDF49\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF]|\uD80D[\uDC00-\uDC2E]|\uD87E[\uDC00-\uDE1D]|\uD81B[\uDF00-\uDF44\uDF50\uDF93-\uDF9F]|[\uD80C\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872][\uDC00-\uDFFF]|\uD805[\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDD80-\uDDAE\uDDD8-\uDDDB\uDE00-\uDE2F\uDE44\uDE80-\uDEAA\uDF00-\uDF19]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF63-\uDF77\uDF7D-\uDF8F]|\uD809[\uDC80-\uDD43]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDCE0-\uDCF2\uDCF4\uDCF5\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB]|\uD804[\uDC03-\uDC37\uDC83-\uDCAF\uDCD0-\uDCE8\uDD03-\uDD26\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDDA\uDDDC\uDE00-\uDE11\uDE13-\uDE2B\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA8\uDEB0-\uDEDE\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF50\uDF5D-\uDF61]|\uD808[\uDC00-\uDF99]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD806[\uDCA0-\uDCDF\uDCFF\uDEC0-\uDEF8]|\uD811[\uDC00-\uDE46]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD82C[\uDC00\uDC01]|\uD873[\uDC00-\uDEA1]' + }, + 'M': { + bmp: '\u0300-\u036F\u0483-\u0489\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u0610-\u061A\u064B-\u065F\u0670\u06D6-\u06DC\u06DF-\u06E4\u06E7\u06E8\u06EA-\u06ED\u0711\u0730-\u074A\u07A6-\u07B0\u07EB-\u07F3\u0816-\u0819\u081B-\u0823\u0825-\u0827\u0829-\u082D\u0859-\u085B\u08E3-\u0903\u093A-\u093C\u093E-\u094F\u0951-\u0957\u0962\u0963\u0981-\u0983\u09BC\u09BE-\u09C4\u09C7\u09C8\u09CB-\u09CD\u09D7\u09E2\u09E3\u0A01-\u0A03\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A70\u0A71\u0A75\u0A81-\u0A83\u0ABC\u0ABE-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AE2\u0AE3\u0B01-\u0B03\u0B3C\u0B3E-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B62\u0B63\u0B82\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD7\u0C00-\u0C03\u0C3E-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C62\u0C63\u0C81-\u0C83\u0CBC\u0CBE-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CE2\u0CE3\u0D01-\u0D03\u0D3E-\u0D44\u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0D62\u0D63\u0D82\u0D83\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DF2\u0DF3\u0E31\u0E34-\u0E3A\u0E47-\u0E4E\u0EB1\u0EB4-\u0EB9\u0EBB\u0EBC\u0EC8-\u0ECD\u0F18\u0F19\u0F35\u0F37\u0F39\u0F3E\u0F3F\u0F71-\u0F84\u0F86\u0F87\u0F8D-\u0F97\u0F99-\u0FBC\u0FC6\u102B-\u103E\u1056-\u1059\u105E-\u1060\u1062-\u1064\u1067-\u106D\u1071-\u1074\u1082-\u108D\u108F\u109A-\u109D\u135D-\u135F\u1712-\u1714\u1732-\u1734\u1752\u1753\u1772\u1773\u17B4-\u17D3\u17DD\u180B-\u180D\u18A9\u1920-\u192B\u1930-\u193B\u1A17-\u1A1B\u1A55-\u1A5E\u1A60-\u1A7C\u1A7F\u1AB0-\u1ABE\u1B00-\u1B04\u1B34-\u1B44\u1B6B-\u1B73\u1B80-\u1B82\u1BA1-\u1BAD\u1BE6-\u1BF3\u1C24-\u1C37\u1CD0-\u1CD2\u1CD4-\u1CE8\u1CED\u1CF2-\u1CF4\u1CF8\u1CF9\u1DC0-\u1DF5\u1DFC-\u1DFF\u20D0-\u20F0\u2CEF-\u2CF1\u2D7F\u2DE0-\u2DFF\u302A-\u302F\u3099\u309A\uA66F-\uA672\uA674-\uA67D\uA69E\uA69F\uA6F0\uA6F1\uA802\uA806\uA80B\uA823-\uA827\uA880\uA881\uA8B4-\uA8C4\uA8E0-\uA8F1\uA926-\uA92D\uA947-\uA953\uA980-\uA983\uA9B3-\uA9C0\uA9E5\uAA29-\uAA36\uAA43\uAA4C\uAA4D\uAA7B-\uAA7D\uAAB0\uAAB2-\uAAB4\uAAB7\uAAB8\uAABE\uAABF\uAAC1\uAAEB-\uAAEF\uAAF5\uAAF6\uABE3-\uABEA\uABEC\uABED\uFB1E\uFE00-\uFE0F\uFE20-\uFE2F', + astral: '\uD805[\uDCB0-\uDCC3\uDDAF-\uDDB5\uDDB8-\uDDC0\uDDDC\uDDDD\uDE30-\uDE40\uDEAB-\uDEB7\uDF1D-\uDF2B]|\uD834[\uDD65-\uDD69\uDD6D-\uDD72\uDD7B-\uDD82\uDD85-\uDD8B\uDDAA-\uDDAD\uDE42-\uDE44]|\uD804[\uDC00-\uDC02\uDC38-\uDC46\uDC7F-\uDC82\uDCB0-\uDCBA\uDD00-\uDD02\uDD27-\uDD34\uDD73\uDD80-\uDD82\uDDB3-\uDDC0\uDDCA-\uDDCC\uDE2C-\uDE37\uDEDF-\uDEEA\uDF00-\uDF03\uDF3C\uDF3E-\uDF44\uDF47\uDF48\uDF4B-\uDF4D\uDF57\uDF62\uDF63\uDF66-\uDF6C\uDF70-\uDF74]|\uD81B[\uDF51-\uDF7E\uDF8F-\uDF92]|\uD81A[\uDEF0-\uDEF4\uDF30-\uDF36]|\uD82F[\uDC9D\uDC9E]|\uD800[\uDDFD\uDEE0\uDF76-\uDF7A]|\uD836[\uDE00-\uDE36\uDE3B-\uDE6C\uDE75\uDE84\uDE9B-\uDE9F\uDEA1-\uDEAF]|\uD802[\uDE01-\uDE03\uDE05\uDE06\uDE0C-\uDE0F\uDE38-\uDE3A\uDE3F\uDEE5\uDEE6]|\uD83A[\uDCD0-\uDCD6]|\uDB40[\uDD00-\uDDEF]' + }, + 'N': { + bmp: '0-9\xB2\xB3\xB9\xBC-\xBE\u0660-\u0669\u06F0-\u06F9\u07C0-\u07C9\u0966-\u096F\u09E6-\u09EF\u09F4-\u09F9\u0A66-\u0A6F\u0AE6-\u0AEF\u0B66-\u0B6F\u0B72-\u0B77\u0BE6-\u0BF2\u0C66-\u0C6F\u0C78-\u0C7E\u0CE6-\u0CEF\u0D66-\u0D75\u0DE6-\u0DEF\u0E50-\u0E59\u0ED0-\u0ED9\u0F20-\u0F33\u1040-\u1049\u1090-\u1099\u1369-\u137C\u16EE-\u16F0\u17E0-\u17E9\u17F0-\u17F9\u1810-\u1819\u1946-\u194F\u19D0-\u19DA\u1A80-\u1A89\u1A90-\u1A99\u1B50-\u1B59\u1BB0-\u1BB9\u1C40-\u1C49\u1C50-\u1C59\u2070\u2074-\u2079\u2080-\u2089\u2150-\u2182\u2185-\u2189\u2460-\u249B\u24EA-\u24FF\u2776-\u2793\u2CFD\u3007\u3021-\u3029\u3038-\u303A\u3192-\u3195\u3220-\u3229\u3248-\u324F\u3251-\u325F\u3280-\u3289\u32B1-\u32BF\uA620-\uA629\uA6E6-\uA6EF\uA830-\uA835\uA8D0-\uA8D9\uA900-\uA909\uA9D0-\uA9D9\uA9F0-\uA9F9\uAA50-\uAA59\uABF0-\uABF9\uFF10-\uFF19', + astral: '\uD800[\uDD07-\uDD33\uDD40-\uDD78\uDD8A\uDD8B\uDEE1-\uDEFB\uDF20-\uDF23\uDF41\uDF4A\uDFD1-\uDFD5]|\uD801[\uDCA0-\uDCA9]|\uD803[\uDCFA-\uDCFF\uDE60-\uDE7E]|\uD835[\uDFCE-\uDFFF]|\uD83A[\uDCC7-\uDCCF]|\uD81A[\uDE60-\uDE69\uDF50-\uDF59\uDF5B-\uDF61]|\uD806[\uDCE0-\uDCF2]|\uD804[\uDC52-\uDC6F\uDCF0-\uDCF9\uDD36-\uDD3F\uDDD0-\uDDD9\uDDE1-\uDDF4\uDEF0-\uDEF9]|\uD834[\uDF60-\uDF71]|\uD83C[\uDD00-\uDD0C]|\uD809[\uDC00-\uDC6E]|\uD802[\uDC58-\uDC5F\uDC79-\uDC7F\uDCA7-\uDCAF\uDCFB-\uDCFF\uDD16-\uDD1B\uDDBC\uDDBD\uDDC0-\uDDCF\uDDD2-\uDDFF\uDE40-\uDE47\uDE7D\uDE7E\uDE9D-\uDE9F\uDEEB-\uDEEF\uDF58-\uDF5F\uDF78-\uDF7F\uDFA9-\uDFAF]|\uD805[\uDCD0-\uDCD9\uDE50-\uDE59\uDEC0-\uDEC9\uDF30-\uDF3B]' + }, + 'P': { + bmp: '\x21-\x23\x25-\\x2A\x2C-\x2F\x3A\x3B\\x3F\x40\\x5B-\\x5D\x5F\\x7B\x7D\xA1\xA7\xAB\xB6\xB7\xBB\xBF\u037E\u0387\u055A-\u055F\u0589\u058A\u05BE\u05C0\u05C3\u05C6\u05F3\u05F4\u0609\u060A\u060C\u060D\u061B\u061E\u061F\u066A-\u066D\u06D4\u0700-\u070D\u07F7-\u07F9\u0830-\u083E\u085E\u0964\u0965\u0970\u0AF0\u0DF4\u0E4F\u0E5A\u0E5B\u0F04-\u0F12\u0F14\u0F3A-\u0F3D\u0F85\u0FD0-\u0FD4\u0FD9\u0FDA\u104A-\u104F\u10FB\u1360-\u1368\u1400\u166D\u166E\u169B\u169C\u16EB-\u16ED\u1735\u1736\u17D4-\u17D6\u17D8-\u17DA\u1800-\u180A\u1944\u1945\u1A1E\u1A1F\u1AA0-\u1AA6\u1AA8-\u1AAD\u1B5A-\u1B60\u1BFC-\u1BFF\u1C3B-\u1C3F\u1C7E\u1C7F\u1CC0-\u1CC7\u1CD3\u2010-\u2027\u2030-\u2043\u2045-\u2051\u2053-\u205E\u207D\u207E\u208D\u208E\u2308-\u230B\u2329\u232A\u2768-\u2775\u27C5\u27C6\u27E6-\u27EF\u2983-\u2998\u29D8-\u29DB\u29FC\u29FD\u2CF9-\u2CFC\u2CFE\u2CFF\u2D70\u2E00-\u2E2E\u2E30-\u2E42\u3001-\u3003\u3008-\u3011\u3014-\u301F\u3030\u303D\u30A0\u30FB\uA4FE\uA4FF\uA60D-\uA60F\uA673\uA67E\uA6F2-\uA6F7\uA874-\uA877\uA8CE\uA8CF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA95F\uA9C1-\uA9CD\uA9DE\uA9DF\uAA5C-\uAA5F\uAADE\uAADF\uAAF0\uAAF1\uABEB\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE61\uFE63\uFE68\uFE6A\uFE6B\uFF01-\uFF03\uFF05-\uFF0A\uFF0C-\uFF0F\uFF1A\uFF1B\uFF1F\uFF20\uFF3B-\uFF3D\uFF3F\uFF5B\uFF5D\uFF5F-\uFF65', + astral: '\uD802[\uDC57\uDD1F\uDD3F\uDE50-\uDE58\uDE7F\uDEF0-\uDEF6\uDF39-\uDF3F\uDF99-\uDF9C]|\uD809[\uDC70-\uDC74]|\uD805[\uDCC6\uDDC1-\uDDD7\uDE41-\uDE43\uDF3C-\uDF3E]|\uD836[\uDE87-\uDE8B]|\uD801\uDD6F|\uD82F\uDC9F|\uD804[\uDC47-\uDC4D\uDCBB\uDCBC\uDCBE-\uDCC1\uDD40-\uDD43\uDD74\uDD75\uDDC5-\uDDC9\uDDCD\uDDDB\uDDDD-\uDDDF\uDE38-\uDE3D\uDEA9]|\uD800[\uDD00-\uDD02\uDF9F\uDFD0]|\uD81A[\uDE6E\uDE6F\uDEF5\uDF37-\uDF3B\uDF44]' + }, + 'S': { + bmp: '\\x24\\x2B\x3C-\x3E\\x5E\x60\\x7C\x7E\xA2-\xA6\xA8\xA9\xAC\xAE-\xB1\xB4\xB8\xD7\xF7\u02C2-\u02C5\u02D2-\u02DF\u02E5-\u02EB\u02ED\u02EF-\u02FF\u0375\u0384\u0385\u03F6\u0482\u058D-\u058F\u0606-\u0608\u060B\u060E\u060F\u06DE\u06E9\u06FD\u06FE\u07F6\u09F2\u09F3\u09FA\u09FB\u0AF1\u0B70\u0BF3-\u0BFA\u0C7F\u0D79\u0E3F\u0F01-\u0F03\u0F13\u0F15-\u0F17\u0F1A-\u0F1F\u0F34\u0F36\u0F38\u0FBE-\u0FC5\u0FC7-\u0FCC\u0FCE\u0FCF\u0FD5-\u0FD8\u109E\u109F\u1390-\u1399\u17DB\u1940\u19DE-\u19FF\u1B61-\u1B6A\u1B74-\u1B7C\u1FBD\u1FBF-\u1FC1\u1FCD-\u1FCF\u1FDD-\u1FDF\u1FED-\u1FEF\u1FFD\u1FFE\u2044\u2052\u207A-\u207C\u208A-\u208C\u20A0-\u20BE\u2100\u2101\u2103-\u2106\u2108\u2109\u2114\u2116-\u2118\u211E-\u2123\u2125\u2127\u2129\u212E\u213A\u213B\u2140-\u2144\u214A-\u214D\u214F\u218A\u218B\u2190-\u2307\u230C-\u2328\u232B-\u23FA\u2400-\u2426\u2440-\u244A\u249C-\u24E9\u2500-\u2767\u2794-\u27C4\u27C7-\u27E5\u27F0-\u2982\u2999-\u29D7\u29DC-\u29FB\u29FE-\u2B73\u2B76-\u2B95\u2B98-\u2BB9\u2BBD-\u2BC8\u2BCA-\u2BD1\u2BEC-\u2BEF\u2CE5-\u2CEA\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u2FF0-\u2FFB\u3004\u3012\u3013\u3020\u3036\u3037\u303E\u303F\u309B\u309C\u3190\u3191\u3196-\u319F\u31C0-\u31E3\u3200-\u321E\u322A-\u3247\u3250\u3260-\u327F\u328A-\u32B0\u32C0-\u32FE\u3300-\u33FF\u4DC0-\u4DFF\uA490-\uA4C6\uA700-\uA716\uA720\uA721\uA789\uA78A\uA828-\uA82B\uA836-\uA839\uAA77-\uAA79\uAB5B\uFB29\uFBB2-\uFBC1\uFDFC\uFDFD\uFE62\uFE64-\uFE66\uFE69\uFF04\uFF0B\uFF1C-\uFF1E\uFF3E\uFF40\uFF5C\uFF5E\uFFE0-\uFFE6\uFFE8-\uFFEE\uFFFC\uFFFD', + astral: '\uD83E[\uDC00-\uDC0B\uDC10-\uDC47\uDC50-\uDC59\uDC60-\uDC87\uDC90-\uDCAD\uDD10-\uDD18\uDD80-\uDD84\uDDC0]|\uD83C[\uDC00-\uDC2B\uDC30-\uDC93\uDCA0-\uDCAE\uDCB1-\uDCBF\uDCC1-\uDCCF\uDCD1-\uDCF5\uDD10-\uDD2E\uDD30-\uDD6B\uDD70-\uDD9A\uDDE6-\uDE02\uDE10-\uDE3A\uDE40-\uDE48\uDE50\uDE51\uDF00-\uDFFF]|\uD83D[\uDC00-\uDD79\uDD7B-\uDDA3\uDDA5-\uDED0\uDEE0-\uDEEC\uDEF0-\uDEF3\uDF00-\uDF73\uDF80-\uDFD4]|\uD835[\uDEC1\uDEDB\uDEFB\uDF15\uDF35\uDF4F\uDF6F\uDF89\uDFA9\uDFC3]|\uD800[\uDD37-\uDD3F\uDD79-\uDD89\uDD8C\uDD90-\uDD9B\uDDA0\uDDD0-\uDDFC]|\uD82F\uDC9C|\uD805\uDF3F|\uD802[\uDC77\uDC78\uDEC8]|\uD81A[\uDF3C-\uDF3F\uDF45]|\uD836[\uDC00-\uDDFF\uDE37-\uDE3A\uDE6D-\uDE74\uDE76-\uDE83\uDE85\uDE86]|\uD834[\uDC00-\uDCF5\uDD00-\uDD26\uDD29-\uDD64\uDD6A-\uDD6C\uDD83\uDD84\uDD8C-\uDDA9\uDDAE-\uDDE8\uDE00-\uDE41\uDE45\uDF00-\uDF56]|\uD83B[\uDEF0\uDEF1]' + }, + 'Z': { + bmp: '\x20\xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000' + }, + 'Ahom': { + astral: '\uD805[\uDF00-\uDF19\uDF1D-\uDF2B\uDF30-\uDF3F]' + }, + 'Anatolian_Hieroglyphs' :{ + astral: '\uD811[\uDC00-\uDE46]' + }, + 'Arabic': { + bmp: '\u0600-\u0604\u0606-\u060B\u060D-\u061A\u061E\u0620-\u063F\u0641-\u064A\u0656-\u066F\u0671-\u06DC\u06DE-\u06FF\u0750-\u077F\u08A0-\u08B4\u08E3-\u08FF\uFB50-\uFBC1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFD\uFE70-\uFE74\uFE76-\uFEFC', + astral: '\uD803[\uDE60-\uDE7E]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB\uDEF0\uDEF1]' + }, + 'Armenian': { + bmp: '\u0531-\u0556\u0559-\u055F\u0561-\u0587\u058A\u058D-\u058F\uFB13-\uFB17' + }, + 'Avestan': { + astral: '\uD802[\uDF00-\uDF35\uDF39-\uDF3F]' + }, + 'Balinese': { + bmp: '\u1B00-\u1B4B\u1B50-\u1B7C' + }, + 'Bamum': { + bmp: '\uA6A0-\uA6F7', + astral: '\uD81A[\uDC00-\uDE38]' + }, + 'Bassa_Vah': { + astral: '\uD81A[\uDED0-\uDEED\uDEF0-\uDEF5]' + }, + 'Batak': { + bmp: '\u1BC0-\u1BF3\u1BFC-\u1BFF' + }, + 'Bengali': { + bmp: '\u0980-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC-\u09C4\u09C7\u09C8\u09CB-\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09E6-\u09FB' + }, + 'Bopomofo': { + bmp: '\u02EA\u02EB\u3105-\u312D\u31A0-\u31BA' + }, + 'Brahmi': { + astral: '\uD804[\uDC00-\uDC4D\uDC52-\uDC6F\uDC7F]' + }, + 'Braille': { + bmp: '\u2800-\u28FF' + }, + 'Buginese': { + bmp: '\u1A00-\u1A1B\u1A1E\u1A1F' + }, + 'Buhid': { + bmp: '\u1740-\u1753' + }, + 'Canadian_Aboriginal': { + bmp: '\u1400-\u167F\u18B0-\u18F5' + }, + 'Carian': { + astral: '\uD800[\uDEA0-\uDED0]' + }, + 'Caucasian_Albanian': { + astral: '\uD801[\uDD30-\uDD63\uDD6F]' + }, + 'Chakma': { + astral: '\uD804[\uDD00-\uDD34\uDD36-\uDD43]' + }, + 'Cham': { + bmp: '\uAA00-\uAA36\uAA40-\uAA4D\uAA50-\uAA59\uAA5C-\uAA5F' + }, + 'Cherokee': { + bmp: '\u13A0-\u13F5\u13F8-\u13FD\uAB70-\uABBF' + }, + 'Common': { + bmp: '\0-\x40\\x5B-\x60\\x7B-\xA9\xAB-\xB9\xBB-\xBF\xD7\xF7\u02B9-\u02DF\u02E5-\u02E9\u02EC-\u02FF\u0374\u037E\u0385\u0387\u0589\u0605\u060C\u061B\u061C\u061F\u0640\u06DD\u0964\u0965\u0E3F\u0FD5-\u0FD8\u10FB\u16EB-\u16ED\u1735\u1736\u1802\u1803\u1805\u1CD3\u1CE1\u1CE9-\u1CEC\u1CEE-\u1CF3\u1CF5\u1CF6\u2000-\u200B\u200E-\u2064\u2066-\u2070\u2074-\u207E\u2080-\u208E\u20A0-\u20BE\u2100-\u2125\u2127-\u2129\u212C-\u2131\u2133-\u214D\u214F-\u215F\u2189-\u218B\u2190-\u23FA\u2400-\u2426\u2440-\u244A\u2460-\u27FF\u2900-\u2B73\u2B76-\u2B95\u2B98-\u2BB9\u2BBD-\u2BC8\u2BCA-\u2BD1\u2BEC-\u2BEF\u2E00-\u2E42\u2FF0-\u2FFB\u3000-\u3004\u3006\u3008-\u3020\u3030-\u3037\u303C-\u303F\u309B\u309C\u30A0\u30FB\u30FC\u3190-\u319F\u31C0-\u31E3\u3220-\u325F\u327F-\u32CF\u3358-\u33FF\u4DC0-\u4DFF\uA700-\uA721\uA788-\uA78A\uA830-\uA839\uA92E\uA9CF\uAB5B\uFD3E\uFD3F\uFE10-\uFE19\uFE30-\uFE52\uFE54-\uFE66\uFE68-\uFE6B\uFEFF\uFF01-\uFF20\uFF3B-\uFF40\uFF5B-\uFF65\uFF70\uFF9E\uFF9F\uFFE0-\uFFE6\uFFE8-\uFFEE\uFFF9-\uFFFD', + astral: '\uD83E[\uDC00-\uDC0B\uDC10-\uDC47\uDC50-\uDC59\uDC60-\uDC87\uDC90-\uDCAD\uDD10-\uDD18\uDD80-\uDD84\uDDC0]|\uD82F[\uDCA0-\uDCA3]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDFCB\uDFCE-\uDFFF]|\uDB40[\uDC01\uDC20-\uDC7F]|\uD83D[\uDC00-\uDD79\uDD7B-\uDDA3\uDDA5-\uDED0\uDEE0-\uDEEC\uDEF0-\uDEF3\uDF00-\uDF73\uDF80-\uDFD4]|\uD800[\uDD00-\uDD02\uDD07-\uDD33\uDD37-\uDD3F\uDD90-\uDD9B\uDDD0-\uDDFC\uDEE1-\uDEFB]|\uD834[\uDC00-\uDCF5\uDD00-\uDD26\uDD29-\uDD66\uDD6A-\uDD7A\uDD83\uDD84\uDD8C-\uDDA9\uDDAE-\uDDE8\uDF00-\uDF56\uDF60-\uDF71]|\uD83C[\uDC00-\uDC2B\uDC30-\uDC93\uDCA0-\uDCAE\uDCB1-\uDCBF\uDCC1-\uDCCF\uDCD1-\uDCF5\uDD00-\uDD0C\uDD10-\uDD2E\uDD30-\uDD6B\uDD70-\uDD9A\uDDE6-\uDDFF\uDE01\uDE02\uDE10-\uDE3A\uDE40-\uDE48\uDE50\uDE51\uDF00-\uDFFF]' + }, + 'Coptic': { + bmp: '\u03E2-\u03EF\u2C80-\u2CF3\u2CF9-\u2CFF' + }, + 'Cuneiform': { + astral: '\uD809[\uDC00-\uDC6E\uDC70-\uDC74\uDC80-\uDD43]|\uD808[\uDC00-\uDF99]' + }, + 'Cypriot': { + astral: '\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F]' + }, + 'Cyrillic': { + bmp: '\u0400-\u0484\u0487-\u052F\u1D2B\u1D78\u2DE0-\u2DFF\uA640-\uA69F\uFE2E\uFE2F' + }, + 'Deseret': { + astral: '\uD801[\uDC00-\uDC4F]' + }, + 'Devanagari': { + bmp: '\u0900-\u0950\u0953-\u0963\u0966-\u097F\uA8E0-\uA8FD' + }, + 'Duployan': { + astral: '\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99\uDC9C-\uDC9F]' + }, + 'Egyptian_Hieroglyphs': { + astral: '\uD80C[\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]' + }, + 'Elbasan': { + astral: '\uD801[\uDD00-\uDD27]' + }, + 'Ethiopic': { + bmp: '\u1200-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135D-\u137C\u1380-\u1399\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E' + }, + 'Georgian': { + bmp: '\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u10FF\u2D00-\u2D25\u2D27\u2D2D' + }, + 'Glagolitic': { + bmp: '\u2C00-\u2C2E\u2C30-\u2C5E' + }, + 'Gothic': { + astral: '\uD800[\uDF30-\uDF4A]' + }, + 'Grantha': { + astral: '\uD804[\uDF00-\uDF03\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3C-\uDF44\uDF47\uDF48\uDF4B-\uDF4D\uDF50\uDF57\uDF5D-\uDF63\uDF66-\uDF6C\uDF70-\uDF74]' + }, + 'Greek': { + bmp: '\u0370-\u0373\u0375-\u0377\u037A-\u037D\u037F\u0384\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03E1\u03F0-\u03FF\u1D26-\u1D2A\u1D5D-\u1D61\u1D66-\u1D6A\u1DBF\u1F00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FC4\u1FC6-\u1FD3\u1FD6-\u1FDB\u1FDD-\u1FEF\u1FF2-\u1FF4\u1FF6-\u1FFE\u2126\uAB65', + astral: '\uD800[\uDD40-\uDD8C\uDDA0]|\uD834[\uDE00-\uDE45]' + }, + 'Gujarati': { + bmp: '\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABC-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AD0\u0AE0-\u0AE3\u0AE6-\u0AF1\u0AF9' + }, + 'Gurmukhi': { + bmp: '\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A59-\u0A5C\u0A5E\u0A66-\u0A75' + }, + 'Han': { + bmp: '\u2E80-\u2E99\u2E9B-\u2EF3\u2F00-\u2FD5\u3005\u3007\u3021-\u3029\u3038-\u303B\u3400-\u4DB5\u4E00-\u9FD5\uF900-\uFA6D\uFA70-\uFAD9', + astral: '\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|[\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872][\uDC00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD87E[\uDC00-\uDE1D]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD873[\uDC00-\uDEA1]' + }, + 'Hangul': { + bmp: '\u1100-\u11FF\u302E\u302F\u3131-\u318E\u3200-\u321E\u3260-\u327E\uA960-\uA97C\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uFFA0-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC' + }, + 'Hanunoo': { + bmp: '\u1720-\u1734' + }, + 'Hatran': { + astral: '\uD802[\uDCE0-\uDCF2\uDCF4\uDCF5\uDCFB-\uDCFF]' + }, + 'Hebrew': { + bmp: '\u0591-\u05C7\u05D0-\u05EA\u05F0-\u05F4\uFB1D-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFB4F' + }, + 'Hiragana': { + bmp: '\u3041-\u3096\u309D-\u309F', + astral: '\uD82C\uDC01|\uD83C\uDE00' + }, + 'Imperial_Aramaic': { + astral: '\uD802[\uDC40-\uDC55\uDC57-\uDC5F]' + }, + 'Inherited': { + bmp: '\u0300-\u036F\u0485\u0486\u064B-\u0655\u0670\u0951\u0952\u1AB0-\u1ABE\u1CD0-\u1CD2\u1CD4-\u1CE0\u1CE2-\u1CE8\u1CED\u1CF4\u1CF8\u1CF9\u1DC0-\u1DF5\u1DFC-\u1DFF\u200C\u200D\u20D0-\u20F0\u302A-\u302D\u3099\u309A\uFE00-\uFE0F\uFE20-\uFE2D', + astral: '\uD834[\uDD67-\uDD69\uDD7B-\uDD82\uDD85-\uDD8B\uDDAA-\uDDAD]|\uD800[\uDDFD\uDEE0]|\uDB40[\uDD00-\uDDEF]' + }, + 'Inscriptional_Pahlavi': { + astral: '\uD802[\uDF60-\uDF72\uDF78-\uDF7F]' + }, + 'Inscriptional_Parthian': { + astral: '\uD802[\uDF40-\uDF55\uDF58-\uDF5F]' + }, + 'Javanese': { + bmp: '\uA980-\uA9CD\uA9D0-\uA9D9\uA9DE\uA9DF' + }, + 'Kaithi': { + astral: '\uD804[\uDC80-\uDCC1]' + }, + 'Kannada': { + bmp: '\u0C81-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CDE\u0CE0-\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2' + }, + 'Katakana': { + bmp: '\u30A1-\u30FA\u30FD-\u30FF\u31F0-\u31FF\u32D0-\u32FE\u3300-\u3357\uFF66-\uFF6F\uFF71-\uFF9D', + astral: '\uD82C\uDC00' + }, + 'Kayah_Li': { + bmp: '\uA900-\uA92D\uA92F' + }, + 'Kharoshthi': { + astral: '\uD802[\uDE00-\uDE03\uDE05\uDE06\uDE0C-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE38-\uDE3A\uDE3F-\uDE47\uDE50-\uDE58]' + }, + 'Khmer': { + bmp: '\u1780-\u17DD\u17E0-\u17E9\u17F0-\u17F9\u19E0-\u19FF' + }, + 'Khojki': { + astral: '\uD804[\uDE00-\uDE11\uDE13-\uDE3D]' + }, + 'Khudawadi': { + astral: '\uD804[\uDEB0-\uDEEA\uDEF0-\uDEF9]' + }, + 'Lao': { + bmp: '\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB9\u0EBB-\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC-\u0EDF' + }, + 'Latin': { + bmp: 'A-Za-z\xAA\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02B8\u02E0-\u02E4\u1D00-\u1D25\u1D2C-\u1D5C\u1D62-\u1D65\u1D6B-\u1D77\u1D79-\u1DBE\u1E00-\u1EFF\u2071\u207F\u2090-\u209C\u212A\u212B\u2132\u214E\u2160-\u2188\u2C60-\u2C7F\uA722-\uA787\uA78B-\uA7AD\uA7B0-\uA7B7\uA7F7-\uA7FF\uAB30-\uAB5A\uAB5C-\uAB64\uFB00-\uFB06\uFF21-\uFF3A\uFF41-\uFF5A' + }, + 'Lepcha': { + bmp: '\u1C00-\u1C37\u1C3B-\u1C49\u1C4D-\u1C4F' + }, + 'Limbu': { + bmp: '\u1900-\u191E\u1920-\u192B\u1930-\u193B\u1940\u1944-\u194F' + }, + 'Linear_A': { + astral: '\uD801[\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]' + }, + 'Linear_B': { + astral: '\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA]' + }, + 'Lisu': { + bmp: '\uA4D0-\uA4FF' + }, + 'Lycian': { + astral: '\uD800[\uDE80-\uDE9C]' + }, + 'Lydian': { + astral: '\uD802[\uDD20-\uDD39\uDD3F]' + }, + 'Mahajani': { + astral: '\uD804[\uDD50-\uDD76]' + }, + 'Malayalam': { + bmp: '\u0D01-\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D-\u0D44\u0D46-\u0D48\u0D4A-\u0D4E\u0D57\u0D5F-\u0D63\u0D66-\u0D75\u0D79-\u0D7F' + }, + 'Mandaic': { + bmp: '\u0840-\u085B\u085E' + }, + 'Manichaean': { + astral: '\uD802[\uDEC0-\uDEE6\uDEEB-\uDEF6]' + }, + 'Meetei_Mayek': { + bmp: '\uAAE0-\uAAF6\uABC0-\uABED\uABF0-\uABF9' + }, + 'Mende_Kikakui': { + astral: '\uD83A[\uDC00-\uDCC4\uDCC7-\uDCD6]' + }, + 'Meroitic_Cursive': { + astral: '\uD802[\uDDA0-\uDDB7\uDDBC-\uDDCF\uDDD2-\uDDFF]' + }, + 'Meroitic_Hieroglyphs': { + astral: '\uD802[\uDD80-\uDD9F]' + }, + 'Miao': { + astral: '\uD81B[\uDF00-\uDF44\uDF50-\uDF7E\uDF8F-\uDF9F]' + }, + 'Modi': { + astral: '\uD805[\uDE00-\uDE44\uDE50-\uDE59]' + }, + 'Mongolian': { + bmp: '\u1800\u1801\u1804\u1806-\u180E\u1810-\u1819\u1820-\u1877\u1880-\u18AA' + }, + 'Mro': { + astral: '\uD81A[\uDE40-\uDE5E\uDE60-\uDE69\uDE6E\uDE6F]' + }, + 'Multani': { + astral: '\uD804[\uDE80-\uDE86\uDE88\uDE8A-\uDE8D\uDE8F-\uDE9D\uDE9F-\uDEA9]' + }, + 'Myanmar': { + bmp: '\u1000-\u109F\uA9E0-\uA9FE\uAA60-\uAA7F' + }, + 'Nabataean': { + astral: '\uD802[\uDC80-\uDC9E\uDCA7-\uDCAF]' + }, + 'New_Tai_Lue': { + bmp: '\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19DA\u19DE\u19DF' + }, + 'Nko': { + bmp: '\u07C0-\u07FA' + }, + 'Ogham': { + bmp: '\u1680-\u169C' + }, + 'Ol_Chiki': { + bmp: '\u1C50-\u1C7F' + }, + 'Old_Hungarian': { + astral: '\uD803[\uDC80-\uDCB2\uDCC0-\uDCF2\uDCFA-\uDCFF]' + }, + 'Old_Italic': { + astral: '\uD800[\uDF00-\uDF23]' + }, + 'Old_North_Arabian': { + astral: '\uD802[\uDE80-\uDE9F]' + }, + 'Old_Permic': { + astral: '\uD800[\uDF50-\uDF7A]' + }, + 'Old_Persian': { + astral: '\uD800[\uDFA0-\uDFC3\uDFC8-\uDFD5]' + }, + 'Old_South_Arabian': { + astral: '\uD802[\uDE60-\uDE7F]' + }, + 'Old_Turkic': { + astral: '\uD803[\uDC00-\uDC48]' + }, + 'Oriya': { + bmp: '\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3C-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B5C\u0B5D\u0B5F-\u0B63\u0B66-\u0B77' + }, + 'Osmanya': { + astral: '\uD801[\uDC80-\uDC9D\uDCA0-\uDCA9]' + }, + 'Pahawh_Hmong': { + astral: '\uD81A[\uDF00-\uDF45\uDF50-\uDF59\uDF5B-\uDF61\uDF63-\uDF77\uDF7D-\uDF8F]' + }, + 'Palmyrene': { + astral: '\uD802[\uDC60-\uDC7F]' + }, + 'Pau_Cin_Hau': { + astral: '\uD806[\uDEC0-\uDEF8]' + }, + 'Phags_Pa': { + bmp: '\uA840-\uA877' + }, + 'Phoenician': { + astral: '\uD802[\uDD00-\uDD1B\uDD1F]' + }, + 'Psalter_Pahlavi': { + astral: '\uD802[\uDF80-\uDF91\uDF99-\uDF9C\uDFA9-\uDFAF]' + }, + 'Rejang': { + bmp: '\uA930-\uA953\uA95F' + }, + 'Runic': { + bmp: '\u16A0-\u16EA\u16EE-\u16F8' + }, + 'Samaritan': { + bmp: '\u0800-\u082D\u0830-\u083E' + }, + 'Saurashtra': { + bmp: '\uA880-\uA8C4\uA8CE-\uA8D9' + }, + 'Sharada': { + astral: '\uD804[\uDD80-\uDDCD\uDDD0-\uDDDF]' + }, + 'Shavian': { + astral: '\uD801[\uDC50-\uDC7F]' + }, + 'Siddham': { + astral: '\uD805[\uDD80-\uDDB5\uDDB8-\uDDDD]' + }, + 'SignWriting': { + astral: '\uD836[\uDC00-\uDE8B\uDE9B-\uDE9F\uDEA1-\uDEAF]' + }, + 'Sinhala': { + bmp: '\u0D82\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2-\u0DF4', + astral: '\uD804[\uDDE1-\uDDF4]' + }, + 'Sora_Sompeng': { + astral: '\uD804[\uDCD0-\uDCE8\uDCF0-\uDCF9]' + }, + 'Sundanese': { + bmp: '\u1B80-\u1BBF\u1CC0-\u1CC7' + }, + 'Syloti_Nagri': { + bmp: '\uA800-\uA82B' + }, + 'Syriac': { + bmp: '\u0700-\u070D\u070F-\u074A\u074D-\u074F' + }, + 'Tagalog': { + bmp: '\u1700-\u170C\u170E-\u1714' + }, + 'Tagbanwa': { + bmp: '\u1760-\u176C\u176E-\u1770\u1772\u1773' + }, + 'Tai_Le': { + bmp: '\u1950-\u196D\u1970-\u1974' + }, + 'Tai_Tham': { + bmp: '\u1A20-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AA0-\u1AAD' + }, + 'Tai_Viet': { + bmp: '\uAA80-\uAAC2\uAADB-\uAADF' + }, + 'Takri': { + astral: '\uD805[\uDE80-\uDEB7\uDEC0-\uDEC9]' + }, + 'Tamil': { + bmp: '\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD0\u0BD7\u0BE6-\u0BFA' + }, + 'Telugu': { + bmp: '\u0C00-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C58-\u0C5A\u0C60-\u0C63\u0C66-\u0C6F\u0C78-\u0C7F' + }, + 'Thaana': { + bmp: '\u0780-\u07B1' + }, + 'Thai': { + bmp: '\u0E01-\u0E3A\u0E40-\u0E5B' + }, + 'Tibetan': { + bmp: '\u0F00-\u0F47\u0F49-\u0F6C\u0F71-\u0F97\u0F99-\u0FBC\u0FBE-\u0FCC\u0FCE-\u0FD4\u0FD9\u0FDA' + }, + 'Tifinagh': { + bmp: '\u2D30-\u2D67\u2D6F\u2D70\u2D7F' + }, + 'Tirhuta': { + astral: '\uD805[\uDC80-\uDCC7\uDCD0-\uDCD9]' + }, + 'Ugaritic': { + astral: '\uD800[\uDF80-\uDF9D\uDF9F]' + }, + 'Vai': { + bmp: '\uA500-\uA62B' + }, + 'Warang_Citi': { + astral: '\uD806[\uDCA0-\uDCF2\uDCFF]' + }, + 'Yi': { + bmp: '\uA000-\uA48C\uA490-\uA4C6' + } +}; + +const isValidString = (s) => typeof s == 'string' && s.length > 0; +const buildRegexp = (re) => new RegExp(re.replace(/\\p\{([^}]+)\}/g, function(a, b) { + if (!unicode[b]) throw Error('Invalid expression'); + var i = unicode[b], c = ''; + if (i.bmp) { + c = '[' + i.bmp + ']' + (i.astral ? '|' : ''); + } + if (i.astral) { + c += i.astral; + } + return '(?:' + c + ')'; +})); + +module.exports = { + + unicodelettersonly: function(value, param) { + const arr = ['L']; + + for (let key in param) { + arr.push(key); + } + + return !isValidString(value) || (buildRegexp('^(\\p{' + arr.join('}|\\p{') + '})+$')).test(value) + }, + + unicodescript: function(value, param) { + const arr = param.scripts.slice(0); + + for (let key in param) { + if (key != 'scripts') { + arr.push(key); + } + } + + return !isValidString(value) || (buildRegexp('^(\\p{' + arr.join('}|\\p{') + '})+$')).test(value) + }, + +}; \ No newline at end of file diff --git a/lib/validator/upload.js b/lib/validator/upload.js new file mode 100644 index 0000000..0396794 --- /dev/null +++ b/lib/validator/upload.js @@ -0,0 +1,82 @@ +// IMPROVE: Improve this with the actual file field being checked, for now we check all uploaded files +function getFiles(app, value) { + const files = []; + + if (app.req.files) { + for (let field in app.req.files) { + if (Array.isArray(app.req.files[field])) { + for (let file of app.req.files[field]) { + if (!file.truncated) { + files.push(file); + } + } + } else { + let file = app.req.files[field]; + if (!file.truncated) { + files.push(file); + } + } + } + } + + return files; +} + +module.exports = { + + accept: function(value, param) { + const files = getFiles(this, value); + const allowed = param.replace(/\s/g, '').split(','); + + if (!files.length) { + return true; + } + + for (let file of files) { + if (!allowed.some(allow => { + if (allow[0] == '.') { + const re = new RegExp(`\\${allow}$`, 'i'); + if (re.test(file.name)) { + return true; + } + } else if (/(audio|video|image)\/\*/i.test(allow)) { + const re = new RegExp(`^${allow.replace('*', '.*')}$`, 'i'); + if (re.test(file.mimetype)) { + return true; + } + } else if (allow.toLowerCase() == file.mimetype.toLowerCase()) { + return true; + } + })) { + return false; + } + } + + return true; + }, + + minsize: function(value, param) { + return !getFiles(this, value).some(file => file.size < param); + }, + + maxsize: function(value, param) { + return !getFiles(this, value).some(file => file.size > param); + }, + + mintotalsize: function(value, param) { + return getFiles(this, value).reduce((size, file) => size + file.size, 0) >= param; + }, + + maxtotalsize: function(value, param) { + return getFiles(this, value).reduce((size, file) => size + file.size, 0) <= param; + }, + + minfiles: function(value, param) { + return getFiles(this, value).length >= param; + }, + + maxfiles: function(value, param) { + return getFiles(this, value).length <= param; + }, + +}; \ No newline at end of file diff --git a/lib/webhooks/stripe.js b/lib/webhooks/stripe.js new file mode 100644 index 0000000..7c8c3d0 --- /dev/null +++ b/lib/webhooks/stripe.js @@ -0,0 +1,22 @@ +const webhook = require('../core/webhook'); +const config = require('../setup/config'); +const fs = require('fs-extra') + +if (fs.existsSync('app/webhooks/stripe')) { + const stripe = require('stripe')(config.stripe.secretKey); + const endpointSecret = config.stripe.endpointSecret; + + exports.handler = webhook.createHandler('stripe', (req, res, next) => { + const sig = req.headers['stripe-signature']; + + try { + stripe.webhooks.constructEvent(req.rawBody, sig, endpointSecret); + } catch (err) { + res.status(400).send(`Webhook Error: ${err.message}`); + return false; + } + + // return the action name to execute + return req.body.type; + }); +} \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..a4a5251 --- /dev/null +++ b/package.json @@ -0,0 +1,51 @@ +{ + "name": "rpm_access", + "version": "1.6.0", + "private": true, + "description": "", + "main": "index.js", + "engines": { + "node": ">=14.15.0" + }, + "scripts": { + "start": "node ./index.js" + }, + "author": "Wappler", + "license": "ISC", + "dependencies": { + "archiver": "^5.3.0", + "compression": "^1.7.4", + "connect-session-knex": "^3.0.0", + "cookie-parser": "^1.4.6", + "cors": "^2.8.5", + "debug": "^4.3.2", + "dotenv": "^16.0.3", + "ejs": "^3.1.6", + "express": "^4.17.1", + "express-end": "0.0.8", + "express-fileupload": "^1.2.1", + "express-session": "^1.17.2", + "follow-redirects": "^1.14.5", + "fs-extra": "^10.0.0", + "http-proxy": "^1.18.1", + "knex": "^2.3.0", + "mime-types": "^2.1.34", + "node-schedule": "^2.0.0", + "nodemon": "^2.0.15", + "qs": "^6.10.1", + "session-file-store": "^1.5.0", + "socket.io": "^4.6.1", + "unzipper": "^0.10.11", + "uuid": "^9.0.0" + }, + "nodemonConfig": { + "watch": [ + "app", + "lib", + "views", + "extensions", + "tmp/**/restart.txt" + ], + "ext": "ejs,js,json" + } +} \ No newline at end of file diff --git a/public/bootstrap/5/css/bootstrap.min.css b/public/bootstrap/5/css/bootstrap.min.css new file mode 100644 index 0000000..1472dec --- /dev/null +++ b/public/bootstrap/5/css/bootstrap.min.css @@ -0,0 +1,7 @@ +@charset "UTF-8";/*! + * Bootstrap v5.1.3 (https://getbootstrap.com/) + * Copyright 2011-2021 The Bootstrap Authors + * Copyright 2011-2021 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */:root{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-primary-rgb:13,110,253;--bs-secondary-rgb:108,117,125;--bs-success-rgb:25,135,84;--bs-info-rgb:13,202,240;--bs-warning-rgb:255,193,7;--bs-danger-rgb:220,53,69;--bs-light-rgb:248,249,250;--bs-dark-rgb:33,37,41;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-body-color-rgb:33,37,41;--bs-body-bg-rgb:255,255,255;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-bg:#fff}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.875em}.mark,mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::-webkit-file-upload-button{font:inherit}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:#6c757d}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{width:100%;padding-right:var(--bs-gutter-x,.75rem);padding-left:var(--bs-gutter-x,.75rem);margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}@media (min-width:1400px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{max-width:1320px}}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;display:flex;flex-wrap:wrap;margin-top:calc(-1 * var(--bs-gutter-y));margin-right:calc(-.5 * var(--bs-gutter-x));margin-left:calc(-.5 * var(--bs-gutter-x))}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y)}.col{flex:1 0 0%}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.6666666667%}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.33333333%}.col-2{flex:0 0 auto;width:16.66666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.33333333%}.col-5{flex:0 0 auto;width:41.66666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.33333333%}.col-8{flex:0 0 auto;width:66.66666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.33333333%}.col-11{flex:0 0 auto;width:91.66666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:0.25rem}.g-1,.gy-1{--bs-gutter-y:0.25rem}.g-2,.gx-2{--bs-gutter-x:0.5rem}.g-2,.gy-2{--bs-gutter-y:0.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (min-width:576px){.col-sm{flex:1 0 0%}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.6666666667%}.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.33333333%}.col-sm-2{flex:0 0 auto;width:16.66666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.33333333%}.col-sm-5{flex:0 0 auto;width:41.66666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.33333333%}.col-sm-8{flex:0 0 auto;width:66.66666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.33333333%}.col-sm-11{flex:0 0 auto;width:91.66666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:0.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:0.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:0.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:0.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (min-width:768px){.col-md{flex:1 0 0%}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.6666666667%}.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:8.33333333%}.col-md-2{flex:0 0 auto;width:16.66666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.33333333%}.col-md-5{flex:0 0 auto;width:41.66666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.33333333%}.col-md-8{flex:0 0 auto;width:66.66666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.33333333%}.col-md-11{flex:0 0 auto;width:91.66666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:0.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:0.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:0.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:0.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (min-width:992px){.col-lg{flex:1 0 0%}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.6666666667%}.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:8.33333333%}.col-lg-2{flex:0 0 auto;width:16.66666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.33333333%}.col-lg-5{flex:0 0 auto;width:41.66666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.33333333%}.col-lg-8{flex:0 0 auto;width:66.66666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.33333333%}.col-lg-11{flex:0 0 auto;width:91.66666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:0.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:0.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:0.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:0.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (min-width:1200px){.col-xl{flex:1 0 0%}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:8.33333333%}.col-xl-2{flex:0 0 auto;width:16.66666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.33333333%}.col-xl-5{flex:0 0 auto;width:41.66666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.33333333%}.col-xl-8{flex:0 0 auto;width:66.66666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.33333333%}.col-xl-11{flex:0 0 auto;width:91.66666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:0.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:0.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:0.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:0.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (min-width:1400px){.col-xxl{flex:1 0 0%}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:8.33333333%}.col-xxl-2{flex:0 0 auto;width:16.66666667%}.col-xxl-3{flex:0 0 auto;width:25%}.col-xxl-4{flex:0 0 auto;width:33.33333333%}.col-xxl-5{flex:0 0 auto;width:41.66666667%}.col-xxl-6{flex:0 0 auto;width:50%}.col-xxl-7{flex:0 0 auto;width:58.33333333%}.col-xxl-8{flex:0 0 auto;width:66.66666667%}.col-xxl-9{flex:0 0 auto;width:75%}.col-xxl-10{flex:0 0 auto;width:83.33333333%}.col-xxl-11{flex:0 0 auto;width:91.66666667%}.col-xxl-12{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333333%}.offset-xxl-2{margin-left:16.66666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.33333333%}.offset-xxl-5{margin-left:41.66666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.33333333%}.offset-xxl-8{margin-left:66.66666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.33333333%}.offset-xxl-11{margin-left:91.66666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:0.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:0.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:0.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:0.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-bg:transparent;--bs-table-accent-bg:transparent;--bs-table-striped-color:#212529;--bs-table-striped-bg:rgba(0, 0, 0, 0.05);--bs-table-active-color:#212529;--bs-table-active-bg:rgba(0, 0, 0, 0.1);--bs-table-hover-color:#212529;--bs-table-hover-bg:rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;color:#212529;vertical-align:top;border-color:#dee2e6}.table>:not(caption)>*>*{padding:.5rem .5rem;background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg)}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table>:not(:first-child){border-top:2px solid currentColor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-borderless>:not(:first-child){border-top-width:0}.table-striped>tbody>tr:nth-of-type(odd)>*{--bs-table-accent-bg:var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg:var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover>*{--bs-table-accent-bg:var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-bg:#cfe2ff;--bs-table-striped-bg:#c5d7f2;--bs-table-striped-color:#000;--bs-table-active-bg:#bacbe6;--bs-table-active-color:#000;--bs-table-hover-bg:#bfd1ec;--bs-table-hover-color:#000;color:#000;border-color:#bacbe6}.table-secondary{--bs-table-bg:#e2e3e5;--bs-table-striped-bg:#d7d8da;--bs-table-striped-color:#000;--bs-table-active-bg:#cbccce;--bs-table-active-color:#000;--bs-table-hover-bg:#d1d2d4;--bs-table-hover-color:#000;color:#000;border-color:#cbccce}.table-success{--bs-table-bg:#d1e7dd;--bs-table-striped-bg:#c7dbd2;--bs-table-striped-color:#000;--bs-table-active-bg:#bcd0c7;--bs-table-active-color:#000;--bs-table-hover-bg:#c1d6cc;--bs-table-hover-color:#000;color:#000;border-color:#bcd0c7}.table-info{--bs-table-bg:#cff4fc;--bs-table-striped-bg:#c5e8ef;--bs-table-striped-color:#000;--bs-table-active-bg:#badce3;--bs-table-active-color:#000;--bs-table-hover-bg:#bfe2e9;--bs-table-hover-color:#000;color:#000;border-color:#badce3}.table-warning{--bs-table-bg:#fff3cd;--bs-table-striped-bg:#f2e7c3;--bs-table-striped-color:#000;--bs-table-active-bg:#e6dbb9;--bs-table-active-color:#000;--bs-table-hover-bg:#ece1be;--bs-table-hover-color:#000;color:#000;border-color:#e6dbb9}.table-danger{--bs-table-bg:#f8d7da;--bs-table-striped-bg:#eccccf;--bs-table-striped-color:#000;--bs-table-active-bg:#dfc2c4;--bs-table-active-color:#000;--bs-table-hover-bg:#e5c7ca;--bs-table-hover-color:#000;color:#000;border-color:#dfc2c4}.table-light{--bs-table-bg:#f8f9fa;--bs-table-striped-bg:#ecedee;--bs-table-striped-color:#000;--bs-table-active-bg:#dfe0e1;--bs-table-active-color:#000;--bs-table-hover-bg:#e5e6e7;--bs-table-hover-color:#000;color:#000;border-color:#dfe0e1}.table-dark{--bs-table-bg:#212529;--bs-table-striped-bg:#2c3034;--bs-table-striped-color:#fff;--bs-table-active-bg:#373b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#323539;--bs-table-hover-color:#fff;color:#fff;border-color:#373b3e}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (max-width:575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:#6c757d}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:#212529;background-color:#fff;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-control::-webkit-date-and-time-value{height:1.5em}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}.form-control::file-selector-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#dde0e3}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#dde0e3}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#dde0e3}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + .75rem + 2px)}textarea.form-control-sm{min-height:calc(1.5em + .5rem + 2px)}textarea.form-control-lg{min-height:calc(1.5em + 1rem + 2px)}.form-control-color{width:3rem;height:auto;padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{height:1.5em;border-radius:.25rem}.form-control-color::-webkit-color-swatch{height:1.5em;border-radius:.25rem}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;-moz-padding-start:calc(0.75rem - 3px);font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #212529}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem;border-radius:.2rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem;border-radius:.3rem}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-input{width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:#fff;background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid rgba(0,0,0,.25);-webkit-appearance:none;-moz-appearance:none;appearance:none;-webkit-print-color-adjust:exact;color-adjust:exact}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-check-input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{background-color:#0d6efd;border-color:#0d6efd;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{width:2em;margin-left:-2.5em;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.btn-check:disabled+.btn,.btn-check[disabled]+.btn{pointer-events:none;filter:none;opacity:.65}.form-range{width:100%;height:1.5rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#0d6efd;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#0d6efd;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.form-range:disabled::-moz-range-thumb{background-color:#adb5bd}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-select{height:calc(3.5rem + 2px);line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid transparent;transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control{padding:1rem .75rem}.form-floating>.form-control::-moz-placeholder{color:transparent}.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control:not(:-moz-placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-moz-placeholder-shown)~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:-webkit-autofill~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus{z-index:3}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:3}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-lg>.btn,.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group-sm>.btn,.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#198754}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(25,135,84,.9);border-radius:.25rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#198754;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-valid,.was-validated .form-select:valid{border-color:#198754}.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"],.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-valid:focus,.was-validated .form-select:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid,.was-validated .form-check-input:valid{border-color:#198754}.form-check-input.is-valid:checked,.was-validated .form-check-input:valid:checked{background-color:#198754}.form-check-input.is-valid:focus,.was-validated .form-check-input:valid:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#198754}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.input-group .form-control.is-valid,.input-group .form-select.is-valid,.was-validated .input-group .form-control:valid,.was-validated .input-group .form-select:valid{z-index:1}.input-group .form-control.is-valid:focus,.input-group .form-select.is-valid:focus,.was-validated .input-group .form-control:valid:focus,.was-validated .input-group .form-select:valid:focus{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-invalid,.was-validated .form-select:invalid{border-color:#dc3545}.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"],.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-invalid:focus,.was-validated .form-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid,.was-validated .form-check-input:invalid{border-color:#dc3545}.form-check-input.is-invalid:checked,.was-validated .form-check-input:invalid:checked{background-color:#dc3545}.form-check-input.is-invalid:focus,.was-validated .form-check-input:invalid:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.input-group .form-control.is-invalid,.input-group .form-select.is-invalid,.was-validated .input-group .form-control:invalid,.was-validated .input-group .form-select:invalid{z-index:2}.input-group .form-control.is-invalid:focus,.input-group .form-select.is-invalid:focus,.was-validated .input-group .form-control:invalid:focus,.was-validated .input-group .form-select:invalid:focus{z-index:3}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#212529;text-align:center;text-decoration:none;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529}.btn-check:focus+.btn,.btn:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.btn.disabled,.btn:disabled,fieldset:disabled .btn{pointer-events:none;opacity:.65}.btn-primary{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-primary:hover{color:#fff;background-color:#0b5ed7;border-color:#0a58ca}.btn-check:focus+.btn-primary,.btn-primary:focus{color:#fff;background-color:#0b5ed7;border-color:#0a58ca;box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-check:active+.btn-primary,.btn-check:checked+.btn-primary,.btn-primary.active,.btn-primary:active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0a58ca;border-color:#0a53be}.btn-check:active+.btn-primary:focus,.btn-check:checked+.btn-primary:focus,.btn-primary.active:focus,.btn-primary:active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5c636a;border-color:#565e64}.btn-check:focus+.btn-secondary,.btn-secondary:focus{color:#fff;background-color:#5c636a;border-color:#565e64;box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-check:active+.btn-secondary,.btn-check:checked+.btn-secondary,.btn-secondary.active,.btn-secondary:active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#565e64;border-color:#51585e}.btn-check:active+.btn-secondary:focus,.btn-check:checked+.btn-secondary:focus,.btn-secondary.active:focus,.btn-secondary:active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-success{color:#fff;background-color:#198754;border-color:#198754}.btn-success:hover{color:#fff;background-color:#157347;border-color:#146c43}.btn-check:focus+.btn-success,.btn-success:focus{color:#fff;background-color:#157347;border-color:#146c43;box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-check:active+.btn-success,.btn-check:checked+.btn-success,.btn-success.active,.btn-success:active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#146c43;border-color:#13653f}.btn-check:active+.btn-success:focus,.btn-check:checked+.btn-success:focus,.btn-success.active:focus,.btn-success:active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#198754;border-color:#198754}.btn-info{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-info:hover{color:#000;background-color:#31d2f2;border-color:#25cff2}.btn-check:focus+.btn-info,.btn-info:focus{color:#000;background-color:#31d2f2;border-color:#25cff2;box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-check:active+.btn-info,.btn-check:checked+.btn-info,.btn-info.active,.btn-info:active,.show>.btn-info.dropdown-toggle{color:#000;background-color:#3dd5f3;border-color:#25cff2}.btn-check:active+.btn-info:focus,.btn-check:checked+.btn-info:focus,.btn-info.active:focus,.btn-info:active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-info.disabled,.btn-info:disabled{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-warning{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#000;background-color:#ffca2c;border-color:#ffc720}.btn-check:focus+.btn-warning,.btn-warning:focus{color:#000;background-color:#ffca2c;border-color:#ffc720;box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-check:active+.btn-warning,.btn-check:checked+.btn-warning,.btn-warning.active,.btn-warning:active,.show>.btn-warning.dropdown-toggle{color:#000;background-color:#ffcd39;border-color:#ffc720}.btn-check:active+.btn-warning:focus,.btn-check:checked+.btn-warning:focus,.btn-warning.active:focus,.btn-warning:active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#bb2d3b;border-color:#b02a37}.btn-check:focus+.btn-danger,.btn-danger:focus{color:#fff;background-color:#bb2d3b;border-color:#b02a37;box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-check:active+.btn-danger,.btn-check:checked+.btn-danger,.btn-danger.active,.btn-danger:active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#b02a37;border-color:#a52834}.btn-check:active+.btn-danger:focus,.btn-check:checked+.btn-danger:focus,.btn-danger.active:focus,.btn-danger:active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-light{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:focus+.btn-light,.btn-light:focus{color:#000;background-color:#f9fafb;border-color:#f9fafb;box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-check:active+.btn-light,.btn-check:checked+.btn-light,.btn-light.active,.btn-light:active,.show>.btn-light.dropdown-toggle{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:active+.btn-light:focus,.btn-check:checked+.btn-light:focus,.btn-light.active:focus,.btn-light:active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-light.disabled,.btn-light:disabled{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-dark{color:#fff;background-color:#212529;border-color:#212529}.btn-dark:hover{color:#fff;background-color:#1c1f23;border-color:#1a1e21}.btn-check:focus+.btn-dark,.btn-dark:focus{color:#fff;background-color:#1c1f23;border-color:#1a1e21;box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-check:active+.btn-dark,.btn-check:checked+.btn-dark,.btn-dark.active,.btn-dark:active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1a1e21;border-color:#191c1f}.btn-check:active+.btn-dark:focus,.btn-check:checked+.btn-dark:focus,.btn-dark.active:focus,.btn-dark:active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#212529;border-color:#212529}.btn-outline-primary{color:#0d6efd;border-color:#0d6efd}.btn-outline-primary:hover{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:focus+.btn-outline-primary,.btn-outline-primary:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-check:active+.btn-outline-primary,.btn-check:checked+.btn-outline-primary,.btn-outline-primary.active,.btn-outline-primary.dropdown-toggle.show,.btn-outline-primary:active{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:active+.btn-outline-primary:focus,.btn-check:checked+.btn-outline-primary:focus,.btn-outline-primary.active:focus,.btn-outline-primary.dropdown-toggle.show:focus,.btn-outline-primary:active:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#0d6efd;background-color:transparent}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:focus+.btn-outline-secondary,.btn-outline-secondary:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-check:active+.btn-outline-secondary,.btn-check:checked+.btn-outline-secondary,.btn-outline-secondary.active,.btn-outline-secondary.dropdown-toggle.show,.btn-outline-secondary:active{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:active+.btn-outline-secondary:focus,.btn-check:checked+.btn-outline-secondary:focus,.btn-outline-secondary.active:focus,.btn-outline-secondary.dropdown-toggle.show:focus,.btn-outline-secondary:active:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-success{color:#198754;border-color:#198754}.btn-outline-success:hover{color:#fff;background-color:#198754;border-color:#198754}.btn-check:focus+.btn-outline-success,.btn-outline-success:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-check:active+.btn-outline-success,.btn-check:checked+.btn-outline-success,.btn-outline-success.active,.btn-outline-success.dropdown-toggle.show,.btn-outline-success:active{color:#fff;background-color:#198754;border-color:#198754}.btn-check:active+.btn-outline-success:focus,.btn-check:checked+.btn-outline-success:focus,.btn-outline-success.active:focus,.btn-outline-success.dropdown-toggle.show:focus,.btn-outline-success:active:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#198754;background-color:transparent}.btn-outline-info{color:#0dcaf0;border-color:#0dcaf0}.btn-outline-info:hover{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:focus+.btn-outline-info,.btn-outline-info:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-check:active+.btn-outline-info,.btn-check:checked+.btn-outline-info,.btn-outline-info.active,.btn-outline-info.dropdown-toggle.show,.btn-outline-info:active{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:active+.btn-outline-info:focus,.btn-check:checked+.btn-outline-info:focus,.btn-outline-info.active:focus,.btn-outline-info.dropdown-toggle.show:focus,.btn-outline-info:active:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#0dcaf0;background-color:transparent}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:focus+.btn-outline-warning,.btn-outline-warning:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-check:active+.btn-outline-warning,.btn-check:checked+.btn-outline-warning,.btn-outline-warning.active,.btn-outline-warning.dropdown-toggle.show,.btn-outline-warning:active{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:active+.btn-outline-warning:focus,.btn-check:checked+.btn-outline-warning:focus,.btn-outline-warning.active:focus,.btn-outline-warning.dropdown-toggle.show:focus,.btn-outline-warning:active:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:focus+.btn-outline-danger,.btn-outline-danger:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-check:active+.btn-outline-danger,.btn-check:checked+.btn-outline-danger,.btn-outline-danger.active,.btn-outline-danger.dropdown-toggle.show,.btn-outline-danger:active{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:active+.btn-outline-danger:focus,.btn-check:checked+.btn-outline-danger:focus,.btn-outline-danger.active:focus,.btn-outline-danger.dropdown-toggle.show:focus,.btn-outline-danger:active:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:focus+.btn-outline-light,.btn-outline-light:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-check:active+.btn-outline-light,.btn-check:checked+.btn-outline-light,.btn-outline-light.active,.btn-outline-light.dropdown-toggle.show,.btn-outline-light:active{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:active+.btn-outline-light:focus,.btn-check:checked+.btn-outline-light:focus,.btn-outline-light.active:focus,.btn-outline-light.dropdown-toggle.show:focus,.btn-outline-light:active:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-dark{color:#212529;border-color:#212529}.btn-outline-dark:hover{color:#fff;background-color:#212529;border-color:#212529}.btn-check:focus+.btn-outline-dark,.btn-outline-dark:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-check:active+.btn-outline-dark,.btn-check:checked+.btn-outline-dark,.btn-outline-dark.active,.btn-outline-dark.dropdown-toggle.show,.btn-outline-dark:active{color:#fff;background-color:#212529;border-color:#212529}.btn-check:active+.btn-outline-dark:focus,.btn-check:checked+.btn-outline-dark:focus,.btn-outline-dark.active:focus,.btn-outline-dark.dropdown-toggle.show:focus,.btn-outline-dark:active:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#212529;background-color:transparent}.btn-link{font-weight:400;color:#0d6efd;text-decoration:underline}.btn-link:hover{color:#0a58ca}.btn-link.disabled,.btn-link:disabled{color:#6c757d}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media (prefers-reduced-motion:reduce){.collapsing.collapse-horizontal{transition:none}}.dropdown,.dropend,.dropstart,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;z-index:1000;display:none;min-width:10rem;padding:.5rem 0;margin:0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:.125rem}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid rgba(0,0,0,.15)}.dropdown-item{display:block;width:100%;padding:.25rem 1rem;clear:both;font-weight:400;color:#212529;text-align:inherit;text-decoration:none;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#1e2125;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#0d6efd}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1rem;color:#212529}.dropdown-menu-dark{color:#dee2e6;background-color:#343a40;border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item{color:#dee2e6}.dropdown-menu-dark .dropdown-item:focus,.dropdown-menu-dark .dropdown-item:hover{color:#fff;background-color:rgba(255,255,255,.15)}.dropdown-menu-dark .dropdown-item.active,.dropdown-menu-dark .dropdown-item:active{color:#fff;background-color:#0d6efd}.dropdown-menu-dark .dropdown-item.disabled,.dropdown-menu-dark .dropdown-item:disabled{color:#adb5bd}.dropdown-menu-dark .dropdown-divider{border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item-text{color:#dee2e6}.dropdown-menu-dark .dropdown-header{color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn~.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem;color:#0d6efd;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:focus,.nav-link:hover{color:#0a58ca}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;background:0 0;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{background:0 0;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#0d6efd}.nav-fill .nav-item,.nav-fill>.nav-link{flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-basis:0;flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding-top:.5rem;padding-bottom:.5rem}.navbar>.container,.navbar>.container-fluid,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container-xl,.navbar>.container-xxl{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.navbar-brand{padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;text-decoration:none;white-space:nowrap}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem;transition:box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 .25rem}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (min-width:576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas-header{display:none}.navbar-expand-sm .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-sm .offcanvas-bottom,.navbar-expand-sm .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-sm .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas-header{display:none}.navbar-expand-md .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-md .offcanvas-bottom,.navbar-expand-md .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-md .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas-header{display:none}.navbar-expand-lg .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-lg .offcanvas-bottom,.navbar-expand-lg .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-lg .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas-header{display:none}.navbar-expand-xl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xl .offcanvas-bottom,.navbar-expand-xl .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-xl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xxl .offcanvas-bottom,.navbar-expand-xxl .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-xxl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas-header{display:none}.navbar-expand .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand .offcanvas-bottom,.navbar-expand .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.55)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.55);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.55)}.navbar-light .navbar-text a,.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.55)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.55);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.55)}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;padding:1rem 1rem}.card-title{margin-bottom:.5rem}.card-subtitle{margin-top:-.25rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:1rem}.card-header{padding:.5rem 1rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.5rem 1rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.5rem;margin-bottom:-.5rem;margin-left:-.5rem;border-bottom:0}.card-header-pills{margin-right:-.5rem;margin-left:-.5rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom,.card-img-top{width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-group>.card{margin-bottom:.75rem}@media (min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.accordion-button{position:relative;display:flex;align-items:center;width:100%;padding:1rem 1.25rem;font-size:1rem;color:#212529;text-align:left;background-color:#fff;border:0;border-radius:0;overflow-anchor:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,border-radius .15s ease}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:#0c63e4;background-color:#e7f1ff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.125)}.accordion-button:not(.collapsed)::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230c63e4'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");transform:rotate(-180deg)}.accordion-button::after{flex-shrink:0;width:1.25rem;height:1.25rem;margin-left:auto;content:"";background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-size:1.25rem;transition:transform .2s ease-in-out}@media (prefers-reduced-motion:reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.accordion-header{margin-bottom:0}.accordion-item{background-color:#fff;border:1px solid rgba(0,0,0,.125)}.accordion-item:first-of-type{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.accordion-item:first-of-type .accordion-button{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-body{padding:1rem 1.25rem}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button{border-radius:0}.breadcrumb{display:flex;flex-wrap:wrap;padding:0 0;margin-bottom:1rem;list-style:none}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:var(--bs-breadcrumb-divider, "/")}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;color:#0d6efd;text-decoration:none;background-color:#fff;border:1px solid #dee2e6;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:#0a58ca;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;color:#0a58ca;background-color:#e9ecef;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.page-item:not(:first-child) .page-link{margin-left:-1px}.page-item.active .page-link{z-index:3;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;background-color:#fff;border-color:#dee2e6}.page-link{padding:.375rem .75rem}.page-item:first-child .page-link{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{position:relative;padding:1rem 1rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-primary{color:#084298;background-color:#cfe2ff;border-color:#b6d4fe}.alert-primary .alert-link{color:#06357a}.alert-secondary{color:#41464b;background-color:#e2e3e5;border-color:#d3d6d8}.alert-secondary .alert-link{color:#34383c}.alert-success{color:#0f5132;background-color:#d1e7dd;border-color:#badbcc}.alert-success .alert-link{color:#0c4128}.alert-info{color:#055160;background-color:#cff4fc;border-color:#b6effb}.alert-info .alert-link{color:#04414d}.alert-warning{color:#664d03;background-color:#fff3cd;border-color:#ffecb5}.alert-warning .alert-link{color:#523e02}.alert-danger{color:#842029;background-color:#f8d7da;border-color:#f5c2c7}.alert-danger .alert-link{color:#6a1a21}.alert-light{color:#636464;background-color:#fefefe;border-color:#fdfdfe}.alert-light .alert-link{color:#4f5050}.alert-dark{color:#141619;background-color:#d3d3d4;border-color:#bcbebf}.alert-dark .alert-link{color:#101214}@-webkit-keyframes progress-bar-stripes{0%{background-position-x:1rem}}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress{display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#0d6efd;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:1s linear infinite progress-bar-stripes;animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.list-group{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>li::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.5rem 1rem;color:#212529;text-decoration:none;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#084298;background-color:#cfe2ff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#084298;background-color:#bacbe6}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#084298;border-color:#084298}.list-group-item-secondary{color:#41464b;background-color:#e2e3e5}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#41464b;background-color:#cbccce}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#41464b;border-color:#41464b}.list-group-item-success{color:#0f5132;background-color:#d1e7dd}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#0f5132;background-color:#bcd0c7}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#0f5132;border-color:#0f5132}.list-group-item-info{color:#055160;background-color:#cff4fc}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#055160;background-color:#badce3}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#055160;border-color:#055160}.list-group-item-warning{color:#664d03;background-color:#fff3cd}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#664d03;background-color:#e6dbb9}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#664d03;border-color:#664d03}.list-group-item-danger{color:#842029;background-color:#f8d7da}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#842029;background-color:#dfc2c4}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#842029;border-color:#842029}.list-group-item-light{color:#636464;background-color:#fefefe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#636464;background-color:#e5e5e5}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#636464;border-color:#636464}.list-group-item-dark{color:#141619;background-color:#d3d3d4}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#141619;background-color:#bebebf}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#141619;border-color:#141619}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:#000;background:transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;border:0;border-radius:.25rem;opacity:.5}.btn-close:hover{color:#000;text-decoration:none;opacity:.75}.btn-close:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25);opacity:1}.btn-close.disabled,.btn-close:disabled{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;opacity:.25}.btn-close-white{filter:invert(1) grayscale(100%) brightness(200%)}.toast{width:350px;max-width:100%;font-size:.875rem;pointer-events:auto;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .5rem 1rem rgba(0,0,0,.15);border-radius:.25rem}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:.75rem}.toast-header{display:flex;align-items:center;padding:.5rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.toast-header .btn-close{margin-right:-.375rem;margin-left:.75rem}.toast-body{padding:.75rem;word-wrap:break-word}.modal{position:fixed;top:0;left:0;z-index:1055;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - 1rem)}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1050;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;flex-shrink:0;align-items:center;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .btn-close{padding:.5rem .5rem;margin:-.5rem -.5rem -.5rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;flex-wrap:wrap;flex-shrink:0;align-items:center;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{height:calc(100% - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}.modal-fullscreen .modal-footer{border-radius:0}@media (max-width:575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}.modal-fullscreen-sm-down .modal-footer{border-radius:0}}@media (max-width:767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}.modal-fullscreen-md-down .modal-footer{border-radius:0}}@media (max-width:991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}.modal-fullscreen-lg-down .modal-footer{border-radius:0}}@media (max-width:1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}.modal-fullscreen-xl-down .modal-footer{border-radius:0}}@media (max-width:1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}.modal-fullscreen-xxl-down .modal-footer{border-radius:0}}.tooltip{position:absolute;z-index:1080;display:block;margin:0;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .tooltip-arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[data-popper-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow,.bs-tooltip-top .tooltip-arrow{bottom:0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before,.bs-tooltip-top .tooltip-arrow::before{top:-1px;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[data-popper-placement^=right],.bs-tooltip-end{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow,.bs-tooltip-end .tooltip-arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before,.bs-tooltip-end .tooltip-arrow::before{right:-1px;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[data-popper-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow,.bs-tooltip-bottom .tooltip-arrow{top:0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before,.bs-tooltip-bottom .tooltip-arrow::before{bottom:-1px;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[data-popper-placement^=left],.bs-tooltip-start{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow,.bs-tooltip-start .tooltip-arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before,.bs-tooltip-start .tooltip-arrow::before{left:-1px;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1070;display:block;max-width:276px;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .popover-arrow{position:absolute;display:block;width:1rem;height:.5rem}.popover .popover-arrow::after,.popover .popover-arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow,.bs-popover-top>.popover-arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-top>.popover-arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow,.bs-popover-end>.popover-arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-end>.popover-arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow,.bs-popover-bottom>.popover-arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f0f0f0}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow,.bs-popover-start>.popover-arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-start>.popover-arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem 1rem;margin-bottom:0;font-size:1rem;background-color:#f0f0f0;border-bottom:1px solid rgba(0,0,0,.2);border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:1rem 1rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-end,.carousel-item-next:not(.carousel-item-start){transform:translateX(100%)}.active.carousel-item-start,.carousel-item-prev:not(.carousel-item-end){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%;list-style:none}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-next-icon,.carousel-dark .carousel-control-prev-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}@-webkit-keyframes spinner-border{to{transform:rotate(360deg)}}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:.75s linear infinite spinner-border;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:.75s linear infinite spinner-grow;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{-webkit-animation-duration:1.5s;animation-duration:1.5s}}.offcanvas{position:fixed;bottom:0;z-index:1045;display:flex;flex-direction:column;max-width:100%;visibility:hidden;background-color:#fff;background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;align-items:center;justify-content:space-between;padding:1rem 1rem}.offcanvas-header .btn-close{padding:.5rem .5rem;margin-top:-.5rem;margin-right:-.5rem;margin-bottom:-.5rem}.offcanvas-title{margin-bottom:0;line-height:1.5}.offcanvas-body{flex-grow:1;padding:1rem 1rem;overflow-y:auto}.offcanvas-start{top:0;left:0;width:400px;border-right:1px solid rgba(0,0,0,.2);transform:translateX(-100%)}.offcanvas-end{top:0;right:0;width:400px;border-left:1px solid rgba(0,0,0,.2);transform:translateX(100%)}.offcanvas-top{top:0;right:0;left:0;height:30vh;max-height:100%;border-bottom:1px solid rgba(0,0,0,.2);transform:translateY(-100%)}.offcanvas-bottom{right:0;left:0;height:30vh;max-height:100%;border-top:1px solid rgba(0,0,0,.2);transform:translateY(100%)}.offcanvas.show{transform:none}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentColor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{-webkit-animation:placeholder-glow 2s ease-in-out infinite;animation:placeholder-glow 2s ease-in-out infinite}@-webkit-keyframes placeholder-glow{50%{opacity:.2}}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{-webkit-mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);-webkit-mask-size:200% 100%;mask-size:200% 100%;-webkit-animation:placeholder-wave 2s linear infinite;animation:placeholder-wave 2s linear infinite}@-webkit-keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}@keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.link-primary{color:#0d6efd}.link-primary:focus,.link-primary:hover{color:#0a58ca}.link-secondary{color:#6c757d}.link-secondary:focus,.link-secondary:hover{color:#565e64}.link-success{color:#198754}.link-success:focus,.link-success:hover{color:#146c43}.link-info{color:#0dcaf0}.link-info:focus,.link-info:hover{color:#3dd5f3}.link-warning{color:#ffc107}.link-warning:focus,.link-warning:hover{color:#ffcd39}.link-danger{color:#dc3545}.link-danger:focus,.link-danger:hover{color:#b02a37}.link-light{color:#f8f9fa}.link-light:focus,.link-light:hover{color:#f9fafb}.link-dark{color:#212529}.link-dark:focus,.link-dark:hover{color:#1a1e21}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:75%}.ratio-16x9{--bs-aspect-ratio:56.25%}.ratio-21x9{--bs-aspect-ratio:42.8571428571%}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}@media (min-width:576px){.sticky-sm-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:768px){.sticky-md-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:992px){.sticky-lg-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1200px){.sticky-xl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1400px){.sticky-xxl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.hstack{display:flex;flex-direction:row;align-items:center;align-self:stretch}.vstack{display:flex;flex:1 1 auto;flex-direction:column;align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;width:1px;min-height:1em;background-color:currentColor;opacity:.25}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.opacity-0{opacity:0!important}.opacity-25{opacity:.25!important}.opacity-50{opacity:.5!important}.opacity-75{opacity:.75!important}.opacity-100{opacity:1!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translateX(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:1px solid #dee2e6!important}.border-0{border:0!important}.border-top{border-top:1px solid #dee2e6!important}.border-top-0{border-top:0!important}.border-end{border-right:1px solid #dee2e6!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:1px solid #dee2e6!important}.border-start-0{border-left:0!important}.border-primary{border-color:#0d6efd!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#198754!important}.border-info{border-color:#0dcaf0!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#212529!important}.border-white{border-color:#fff!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:1 1 auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-right:0!important;margin-left:0!important}.mx-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-3{margin-right:1rem!important;margin-left:1rem!important}.mx-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-5{margin-right:3rem!important;margin-left:3rem!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-right:0!important;padding-left:0!important}.px-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-3{padding-right:1rem!important;padding-left:1rem!important}.px-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-5{padding-right:3rem!important;padding-left:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-light{font-weight:300!important}.fw-lighter{font-weight:lighter!important}.fw-normal{font-weight:400!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{--bs-text-opacity:1;color:rgba(var(--bs-primary-rgb),var(--bs-text-opacity))!important}.text-secondary{--bs-text-opacity:1;color:rgba(var(--bs-secondary-rgb),var(--bs-text-opacity))!important}.text-success{--bs-text-opacity:1;color:rgba(var(--bs-success-rgb),var(--bs-text-opacity))!important}.text-info{--bs-text-opacity:1;color:rgba(var(--bs-info-rgb),var(--bs-text-opacity))!important}.text-warning{--bs-text-opacity:1;color:rgba(var(--bs-warning-rgb),var(--bs-text-opacity))!important}.text-danger{--bs-text-opacity:1;color:rgba(var(--bs-danger-rgb),var(--bs-text-opacity))!important}.text-light{--bs-text-opacity:1;color:rgba(var(--bs-light-rgb),var(--bs-text-opacity))!important}.text-dark{--bs-text-opacity:1;color:rgba(var(--bs-dark-rgb),var(--bs-text-opacity))!important}.text-black{--bs-text-opacity:1;color:rgba(var(--bs-black-rgb),var(--bs-text-opacity))!important}.text-white{--bs-text-opacity:1;color:rgba(var(--bs-white-rgb),var(--bs-text-opacity))!important}.text-body{--bs-text-opacity:1;color:rgba(var(--bs-body-color-rgb),var(--bs-text-opacity))!important}.text-muted{--bs-text-opacity:1;color:#6c757d!important}.text-black-50{--bs-text-opacity:1;color:rgba(0,0,0,.5)!important}.text-white-50{--bs-text-opacity:1;color:rgba(255,255,255,.5)!important}.text-reset{--bs-text-opacity:1;color:inherit!important}.text-opacity-25{--bs-text-opacity:0.25}.text-opacity-50{--bs-text-opacity:0.5}.text-opacity-75{--bs-text-opacity:0.75}.text-opacity-100{--bs-text-opacity:1}.bg-primary{--bs-bg-opacity:1;background-color:rgba(var(--bs-primary-rgb),var(--bs-bg-opacity))!important}.bg-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-rgb),var(--bs-bg-opacity))!important}.bg-success{--bs-bg-opacity:1;background-color:rgba(var(--bs-success-rgb),var(--bs-bg-opacity))!important}.bg-info{--bs-bg-opacity:1;background-color:rgba(var(--bs-info-rgb),var(--bs-bg-opacity))!important}.bg-warning{--bs-bg-opacity:1;background-color:rgba(var(--bs-warning-rgb),var(--bs-bg-opacity))!important}.bg-danger{--bs-bg-opacity:1;background-color:rgba(var(--bs-danger-rgb),var(--bs-bg-opacity))!important}.bg-light{--bs-bg-opacity:1;background-color:rgba(var(--bs-light-rgb),var(--bs-bg-opacity))!important}.bg-dark{--bs-bg-opacity:1;background-color:rgba(var(--bs-dark-rgb),var(--bs-bg-opacity))!important}.bg-black{--bs-bg-opacity:1;background-color:rgba(var(--bs-black-rgb),var(--bs-bg-opacity))!important}.bg-white{--bs-bg-opacity:1;background-color:rgba(var(--bs-white-rgb),var(--bs-bg-opacity))!important}.bg-body{--bs-bg-opacity:1;background-color:rgba(var(--bs-body-bg-rgb),var(--bs-bg-opacity))!important}.bg-transparent{--bs-bg-opacity:1;background-color:transparent!important}.bg-opacity-10{--bs-bg-opacity:0.1}.bg-opacity-25{--bs-bg-opacity:0.25}.bg-opacity-50{--bs-bg-opacity:0.5}.bg-opacity-75{--bs-bg-opacity:0.75}.bg-opacity-100{--bs-bg-opacity:1}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:.25rem!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:.2rem!important}.rounded-2{border-radius:.25rem!important}.rounded-3{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-end{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-start{border-bottom-left-radius:.25rem!important;border-top-left-radius:.25rem!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media (min-width:576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-right:0!important;margin-left:0!important}.mx-sm-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-sm-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-sm-3{margin-right:1rem!important;margin-left:1rem!important}.mx-sm-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-sm-5{margin-right:3rem!important;margin-left:3rem!important}.mx-sm-auto{margin-right:auto!important;margin-left:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-right:0!important;padding-left:0!important}.px-sm-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-sm-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-sm-3{padding-right:1rem!important;padding-left:1rem!important}.px-sm-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-sm-5{padding-right:3rem!important;padding-left:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-right:0!important;margin-left:0!important}.mx-md-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-md-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-md-3{margin-right:1rem!important;margin-left:1rem!important}.mx-md-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-md-5{margin-right:3rem!important;margin-left:3rem!important}.mx-md-auto{margin-right:auto!important;margin-left:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-right:0!important;padding-left:0!important}.px-md-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-md-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-md-3{padding-right:1rem!important;padding-left:1rem!important}.px-md-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-md-5{padding-right:3rem!important;padding-left:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-right:0!important;margin-left:0!important}.mx-lg-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-lg-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-lg-3{margin-right:1rem!important;margin-left:1rem!important}.mx-lg-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-lg-5{margin-right:3rem!important;margin-left:3rem!important}.mx-lg-auto{margin-right:auto!important;margin-left:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-right:0!important;padding-left:0!important}.px-lg-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-lg-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-lg-3{padding-right:1rem!important;padding-left:1rem!important}.px-lg-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-lg-5{padding-right:3rem!important;padding-left:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-right:0!important;margin-left:0!important}.mx-xl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xl-auto{margin-right:auto!important;margin-left:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-right:0!important;padding-left:0!important}.px-xl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (min-width:1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:1 1 auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-right:0!important;margin-left:0!important}.mx-xxl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xxl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xxl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xxl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xxl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xxl-auto{margin-right:auto!important;margin-left:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-right:0!important;padding-left:0!important}.px-xxl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xxl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xxl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xxl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xxl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (min-width:1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}} +/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/public/bootstrap/5/js/bootstrap.bundle.min.js b/public/bootstrap/5/js/bootstrap.bundle.min.js new file mode 100644 index 0000000..cc0a255 --- /dev/null +++ b/public/bootstrap/5/js/bootstrap.bundle.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v5.1.3 (https://getbootstrap.com/) + * Copyright 2011-2021 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors) + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t="undefined"!=typeof globalThis?globalThis:t||self).bootstrap=e()}(this,(function(){"use strict";const t="transitionend",e=t=>{let e=t.getAttribute("data-bs-target");if(!e||"#"===e){let i=t.getAttribute("href");if(!i||!i.includes("#")&&!i.startsWith("."))return null;i.includes("#")&&!i.startsWith("#")&&(i=`#${i.split("#")[1]}`),e=i&&"#"!==i?i.trim():null}return e},i=t=>{const i=e(t);return i&&document.querySelector(i)?i:null},n=t=>{const i=e(t);return i?document.querySelector(i):null},s=e=>{e.dispatchEvent(new Event(t))},o=t=>!(!t||"object"!=typeof t)&&(void 0!==t.jquery&&(t=t[0]),void 0!==t.nodeType),r=t=>o(t)?t.jquery?t[0]:t:"string"==typeof t&&t.length>0?document.querySelector(t):null,a=(t,e,i)=>{Object.keys(i).forEach((n=>{const s=i[n],r=e[n],a=r&&o(r)?"element":null==(l=r)?`${l}`:{}.toString.call(l).match(/\s([a-z]+)/i)[1].toLowerCase();var l;if(!new RegExp(s).test(a))throw new TypeError(`${t.toUpperCase()}: Option "${n}" provided type "${a}" but expected type "${s}".`)}))},l=t=>!(!o(t)||0===t.getClientRects().length)&&"visible"===getComputedStyle(t).getPropertyValue("visibility"),c=t=>!t||t.nodeType!==Node.ELEMENT_NODE||!!t.classList.contains("disabled")||(void 0!==t.disabled?t.disabled:t.hasAttribute("disabled")&&"false"!==t.getAttribute("disabled")),h=t=>{if(!document.documentElement.attachShadow)return null;if("function"==typeof t.getRootNode){const e=t.getRootNode();return e instanceof ShadowRoot?e:null}return t instanceof ShadowRoot?t:t.parentNode?h(t.parentNode):null},d=()=>{},u=t=>{t.offsetHeight},f=()=>{const{jQuery:t}=window;return t&&!document.body.hasAttribute("data-bs-no-jquery")?t:null},p=[],m=()=>"rtl"===document.documentElement.dir,g=t=>{var e;e=()=>{const e=f();if(e){const i=t.NAME,n=e.fn[i];e.fn[i]=t.jQueryInterface,e.fn[i].Constructor=t,e.fn[i].noConflict=()=>(e.fn[i]=n,t.jQueryInterface)}},"loading"===document.readyState?(p.length||document.addEventListener("DOMContentLoaded",(()=>{p.forEach((t=>t()))})),p.push(e)):e()},_=t=>{"function"==typeof t&&t()},b=(e,i,n=!0)=>{if(!n)return void _(e);const o=(t=>{if(!t)return 0;let{transitionDuration:e,transitionDelay:i}=window.getComputedStyle(t);const n=Number.parseFloat(e),s=Number.parseFloat(i);return n||s?(e=e.split(",")[0],i=i.split(",")[0],1e3*(Number.parseFloat(e)+Number.parseFloat(i))):0})(i)+5;let r=!1;const a=({target:n})=>{n===i&&(r=!0,i.removeEventListener(t,a),_(e))};i.addEventListener(t,a),setTimeout((()=>{r||s(i)}),o)},v=(t,e,i,n)=>{let s=t.indexOf(e);if(-1===s)return t[!i&&n?t.length-1:0];const o=t.length;return s+=i?1:-1,n&&(s=(s+o)%o),t[Math.max(0,Math.min(s,o-1))]},y=/[^.]*(?=\..*)\.|.*/,w=/\..*/,E=/::\d+$/,A={};let T=1;const O={mouseenter:"mouseover",mouseleave:"mouseout"},C=/^(mouseenter|mouseleave)/i,k=new Set(["click","dblclick","mouseup","mousedown","contextmenu","mousewheel","DOMMouseScroll","mouseover","mouseout","mousemove","selectstart","selectend","keydown","keypress","keyup","orientationchange","touchstart","touchmove","touchend","touchcancel","pointerdown","pointermove","pointerup","pointerleave","pointercancel","gesturestart","gesturechange","gestureend","focus","blur","change","reset","select","submit","focusin","focusout","load","unload","beforeunload","resize","move","DOMContentLoaded","readystatechange","error","abort","scroll"]);function L(t,e){return e&&`${e}::${T++}`||t.uidEvent||T++}function x(t){const e=L(t);return t.uidEvent=e,A[e]=A[e]||{},A[e]}function D(t,e,i=null){const n=Object.keys(t);for(let s=0,o=n.length;sfunction(e){if(!e.relatedTarget||e.relatedTarget!==e.delegateTarget&&!e.delegateTarget.contains(e.relatedTarget))return t.call(this,e)};n?n=t(n):i=t(i)}const[o,r,a]=S(e,i,n),l=x(t),c=l[a]||(l[a]={}),h=D(c,r,o?i:null);if(h)return void(h.oneOff=h.oneOff&&s);const d=L(r,e.replace(y,"")),u=o?function(t,e,i){return function n(s){const o=t.querySelectorAll(e);for(let{target:r}=s;r&&r!==this;r=r.parentNode)for(let a=o.length;a--;)if(o[a]===r)return s.delegateTarget=r,n.oneOff&&j.off(t,s.type,e,i),i.apply(r,[s]);return null}}(t,i,n):function(t,e){return function i(n){return n.delegateTarget=t,i.oneOff&&j.off(t,n.type,e),e.apply(t,[n])}}(t,i);u.delegationSelector=o?i:null,u.originalHandler=r,u.oneOff=s,u.uidEvent=d,c[d]=u,t.addEventListener(a,u,o)}function I(t,e,i,n,s){const o=D(e[i],n,s);o&&(t.removeEventListener(i,o,Boolean(s)),delete e[i][o.uidEvent])}function P(t){return t=t.replace(w,""),O[t]||t}const j={on(t,e,i,n){N(t,e,i,n,!1)},one(t,e,i,n){N(t,e,i,n,!0)},off(t,e,i,n){if("string"!=typeof e||!t)return;const[s,o,r]=S(e,i,n),a=r!==e,l=x(t),c=e.startsWith(".");if(void 0!==o){if(!l||!l[r])return;return void I(t,l,r,o,s?i:null)}c&&Object.keys(l).forEach((i=>{!function(t,e,i,n){const s=e[i]||{};Object.keys(s).forEach((o=>{if(o.includes(n)){const n=s[o];I(t,e,i,n.originalHandler,n.delegationSelector)}}))}(t,l,i,e.slice(1))}));const h=l[r]||{};Object.keys(h).forEach((i=>{const n=i.replace(E,"");if(!a||e.includes(n)){const e=h[i];I(t,l,r,e.originalHandler,e.delegationSelector)}}))},trigger(t,e,i){if("string"!=typeof e||!t)return null;const n=f(),s=P(e),o=e!==s,r=k.has(s);let a,l=!0,c=!0,h=!1,d=null;return o&&n&&(a=n.Event(e,i),n(t).trigger(a),l=!a.isPropagationStopped(),c=!a.isImmediatePropagationStopped(),h=a.isDefaultPrevented()),r?(d=document.createEvent("HTMLEvents"),d.initEvent(s,l,!0)):d=new CustomEvent(e,{bubbles:l,cancelable:!0}),void 0!==i&&Object.keys(i).forEach((t=>{Object.defineProperty(d,t,{get:()=>i[t]})})),h&&d.preventDefault(),c&&t.dispatchEvent(d),d.defaultPrevented&&void 0!==a&&a.preventDefault(),d}},M=new Map,H={set(t,e,i){M.has(t)||M.set(t,new Map);const n=M.get(t);n.has(e)||0===n.size?n.set(e,i):console.error(`Bootstrap doesn't allow more than one instance per element. Bound instance: ${Array.from(n.keys())[0]}.`)},get:(t,e)=>M.has(t)&&M.get(t).get(e)||null,remove(t,e){if(!M.has(t))return;const i=M.get(t);i.delete(e),0===i.size&&M.delete(t)}};class B{constructor(t){(t=r(t))&&(this._element=t,H.set(this._element,this.constructor.DATA_KEY,this))}dispose(){H.remove(this._element,this.constructor.DATA_KEY),j.off(this._element,this.constructor.EVENT_KEY),Object.getOwnPropertyNames(this).forEach((t=>{this[t]=null}))}_queueCallback(t,e,i=!0){b(t,e,i)}static getInstance(t){return H.get(r(t),this.DATA_KEY)}static getOrCreateInstance(t,e={}){return this.getInstance(t)||new this(t,"object"==typeof e?e:null)}static get VERSION(){return"5.1.3"}static get NAME(){throw new Error('You have to implement the static method "NAME", for each component!')}static get DATA_KEY(){return`bs.${this.NAME}`}static get EVENT_KEY(){return`.${this.DATA_KEY}`}}const R=(t,e="hide")=>{const i=`click.dismiss${t.EVENT_KEY}`,s=t.NAME;j.on(document,i,`[data-bs-dismiss="${s}"]`,(function(i){if(["A","AREA"].includes(this.tagName)&&i.preventDefault(),c(this))return;const o=n(this)||this.closest(`.${s}`);t.getOrCreateInstance(o)[e]()}))};class W extends B{static get NAME(){return"alert"}close(){if(j.trigger(this._element,"close.bs.alert").defaultPrevented)return;this._element.classList.remove("show");const t=this._element.classList.contains("fade");this._queueCallback((()=>this._destroyElement()),this._element,t)}_destroyElement(){this._element.remove(),j.trigger(this._element,"closed.bs.alert"),this.dispose()}static jQueryInterface(t){return this.each((function(){const e=W.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}R(W,"close"),g(W);const $='[data-bs-toggle="button"]';class z extends B{static get NAME(){return"button"}toggle(){this._element.setAttribute("aria-pressed",this._element.classList.toggle("active"))}static jQueryInterface(t){return this.each((function(){const e=z.getOrCreateInstance(this);"toggle"===t&&e[t]()}))}}function q(t){return"true"===t||"false"!==t&&(t===Number(t).toString()?Number(t):""===t||"null"===t?null:t)}function F(t){return t.replace(/[A-Z]/g,(t=>`-${t.toLowerCase()}`))}j.on(document,"click.bs.button.data-api",$,(t=>{t.preventDefault();const e=t.target.closest($);z.getOrCreateInstance(e).toggle()})),g(z);const U={setDataAttribute(t,e,i){t.setAttribute(`data-bs-${F(e)}`,i)},removeDataAttribute(t,e){t.removeAttribute(`data-bs-${F(e)}`)},getDataAttributes(t){if(!t)return{};const e={};return Object.keys(t.dataset).filter((t=>t.startsWith("bs"))).forEach((i=>{let n=i.replace(/^bs/,"");n=n.charAt(0).toLowerCase()+n.slice(1,n.length),e[n]=q(t.dataset[i])})),e},getDataAttribute:(t,e)=>q(t.getAttribute(`data-bs-${F(e)}`)),offset(t){const e=t.getBoundingClientRect();return{top:e.top+window.pageYOffset,left:e.left+window.pageXOffset}},position:t=>({top:t.offsetTop,left:t.offsetLeft})},V={find:(t,e=document.documentElement)=>[].concat(...Element.prototype.querySelectorAll.call(e,t)),findOne:(t,e=document.documentElement)=>Element.prototype.querySelector.call(e,t),children:(t,e)=>[].concat(...t.children).filter((t=>t.matches(e))),parents(t,e){const i=[];let n=t.parentNode;for(;n&&n.nodeType===Node.ELEMENT_NODE&&3!==n.nodeType;)n.matches(e)&&i.push(n),n=n.parentNode;return i},prev(t,e){let i=t.previousElementSibling;for(;i;){if(i.matches(e))return[i];i=i.previousElementSibling}return[]},next(t,e){let i=t.nextElementSibling;for(;i;){if(i.matches(e))return[i];i=i.nextElementSibling}return[]},focusableChildren(t){const e=["a","button","input","textarea","select","details","[tabindex]",'[contenteditable="true"]'].map((t=>`${t}:not([tabindex^="-"])`)).join(", ");return this.find(e,t).filter((t=>!c(t)&&l(t)))}},K="carousel",X={interval:5e3,keyboard:!0,slide:!1,pause:"hover",wrap:!0,touch:!0},Y={interval:"(number|boolean)",keyboard:"boolean",slide:"(boolean|string)",pause:"(string|boolean)",wrap:"boolean",touch:"boolean"},Q="next",G="prev",Z="left",J="right",tt={ArrowLeft:J,ArrowRight:Z},et="slid.bs.carousel",it="active",nt=".active.carousel-item";class st extends B{constructor(t,e){super(t),this._items=null,this._interval=null,this._activeElement=null,this._isPaused=!1,this._isSliding=!1,this.touchTimeout=null,this.touchStartX=0,this.touchDeltaX=0,this._config=this._getConfig(e),this._indicatorsElement=V.findOne(".carousel-indicators",this._element),this._touchSupported="ontouchstart"in document.documentElement||navigator.maxTouchPoints>0,this._pointerEvent=Boolean(window.PointerEvent),this._addEventListeners()}static get Default(){return X}static get NAME(){return K}next(){this._slide(Q)}nextWhenVisible(){!document.hidden&&l(this._element)&&this.next()}prev(){this._slide(G)}pause(t){t||(this._isPaused=!0),V.findOne(".carousel-item-next, .carousel-item-prev",this._element)&&(s(this._element),this.cycle(!0)),clearInterval(this._interval),this._interval=null}cycle(t){t||(this._isPaused=!1),this._interval&&(clearInterval(this._interval),this._interval=null),this._config&&this._config.interval&&!this._isPaused&&(this._updateInterval(),this._interval=setInterval((document.visibilityState?this.nextWhenVisible:this.next).bind(this),this._config.interval))}to(t){this._activeElement=V.findOne(nt,this._element);const e=this._getItemIndex(this._activeElement);if(t>this._items.length-1||t<0)return;if(this._isSliding)return void j.one(this._element,et,(()=>this.to(t)));if(e===t)return this.pause(),void this.cycle();const i=t>e?Q:G;this._slide(i,this._items[t])}_getConfig(t){return t={...X,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(K,t,Y),t}_handleSwipe(){const t=Math.abs(this.touchDeltaX);if(t<=40)return;const e=t/this.touchDeltaX;this.touchDeltaX=0,e&&this._slide(e>0?J:Z)}_addEventListeners(){this._config.keyboard&&j.on(this._element,"keydown.bs.carousel",(t=>this._keydown(t))),"hover"===this._config.pause&&(j.on(this._element,"mouseenter.bs.carousel",(t=>this.pause(t))),j.on(this._element,"mouseleave.bs.carousel",(t=>this.cycle(t)))),this._config.touch&&this._touchSupported&&this._addTouchEventListeners()}_addTouchEventListeners(){const t=t=>this._pointerEvent&&("pen"===t.pointerType||"touch"===t.pointerType),e=e=>{t(e)?this.touchStartX=e.clientX:this._pointerEvent||(this.touchStartX=e.touches[0].clientX)},i=t=>{this.touchDeltaX=t.touches&&t.touches.length>1?0:t.touches[0].clientX-this.touchStartX},n=e=>{t(e)&&(this.touchDeltaX=e.clientX-this.touchStartX),this._handleSwipe(),"hover"===this._config.pause&&(this.pause(),this.touchTimeout&&clearTimeout(this.touchTimeout),this.touchTimeout=setTimeout((t=>this.cycle(t)),500+this._config.interval))};V.find(".carousel-item img",this._element).forEach((t=>{j.on(t,"dragstart.bs.carousel",(t=>t.preventDefault()))})),this._pointerEvent?(j.on(this._element,"pointerdown.bs.carousel",(t=>e(t))),j.on(this._element,"pointerup.bs.carousel",(t=>n(t))),this._element.classList.add("pointer-event")):(j.on(this._element,"touchstart.bs.carousel",(t=>e(t))),j.on(this._element,"touchmove.bs.carousel",(t=>i(t))),j.on(this._element,"touchend.bs.carousel",(t=>n(t))))}_keydown(t){if(/input|textarea/i.test(t.target.tagName))return;const e=tt[t.key];e&&(t.preventDefault(),this._slide(e))}_getItemIndex(t){return this._items=t&&t.parentNode?V.find(".carousel-item",t.parentNode):[],this._items.indexOf(t)}_getItemByOrder(t,e){const i=t===Q;return v(this._items,e,i,this._config.wrap)}_triggerSlideEvent(t,e){const i=this._getItemIndex(t),n=this._getItemIndex(V.findOne(nt,this._element));return j.trigger(this._element,"slide.bs.carousel",{relatedTarget:t,direction:e,from:n,to:i})}_setActiveIndicatorElement(t){if(this._indicatorsElement){const e=V.findOne(".active",this._indicatorsElement);e.classList.remove(it),e.removeAttribute("aria-current");const i=V.find("[data-bs-target]",this._indicatorsElement);for(let e=0;e{j.trigger(this._element,et,{relatedTarget:o,direction:d,from:s,to:r})};if(this._element.classList.contains("slide")){o.classList.add(h),u(o),n.classList.add(c),o.classList.add(c);const t=()=>{o.classList.remove(c,h),o.classList.add(it),n.classList.remove(it,h,c),this._isSliding=!1,setTimeout(f,0)};this._queueCallback(t,n,!0)}else n.classList.remove(it),o.classList.add(it),this._isSliding=!1,f();a&&this.cycle()}_directionToOrder(t){return[J,Z].includes(t)?m()?t===Z?G:Q:t===Z?Q:G:t}_orderToDirection(t){return[Q,G].includes(t)?m()?t===G?Z:J:t===G?J:Z:t}static carouselInterface(t,e){const i=st.getOrCreateInstance(t,e);let{_config:n}=i;"object"==typeof e&&(n={...n,...e});const s="string"==typeof e?e:n.slide;if("number"==typeof e)i.to(e);else if("string"==typeof s){if(void 0===i[s])throw new TypeError(`No method named "${s}"`);i[s]()}else n.interval&&n.ride&&(i.pause(),i.cycle())}static jQueryInterface(t){return this.each((function(){st.carouselInterface(this,t)}))}static dataApiClickHandler(t){const e=n(this);if(!e||!e.classList.contains("carousel"))return;const i={...U.getDataAttributes(e),...U.getDataAttributes(this)},s=this.getAttribute("data-bs-slide-to");s&&(i.interval=!1),st.carouselInterface(e,i),s&&st.getInstance(e).to(s),t.preventDefault()}}j.on(document,"click.bs.carousel.data-api","[data-bs-slide], [data-bs-slide-to]",st.dataApiClickHandler),j.on(window,"load.bs.carousel.data-api",(()=>{const t=V.find('[data-bs-ride="carousel"]');for(let e=0,i=t.length;et===this._element));null!==s&&o.length&&(this._selector=s,this._triggerArray.push(e))}this._initializeChildren(),this._config.parent||this._addAriaAndCollapsedClass(this._triggerArray,this._isShown()),this._config.toggle&&this.toggle()}static get Default(){return rt}static get NAME(){return ot}toggle(){this._isShown()?this.hide():this.show()}show(){if(this._isTransitioning||this._isShown())return;let t,e=[];if(this._config.parent){const t=V.find(ut,this._config.parent);e=V.find(".collapse.show, .collapse.collapsing",this._config.parent).filter((e=>!t.includes(e)))}const i=V.findOne(this._selector);if(e.length){const n=e.find((t=>i!==t));if(t=n?pt.getInstance(n):null,t&&t._isTransitioning)return}if(j.trigger(this._element,"show.bs.collapse").defaultPrevented)return;e.forEach((e=>{i!==e&&pt.getOrCreateInstance(e,{toggle:!1}).hide(),t||H.set(e,"bs.collapse",null)}));const n=this._getDimension();this._element.classList.remove(ct),this._element.classList.add(ht),this._element.style[n]=0,this._addAriaAndCollapsedClass(this._triggerArray,!0),this._isTransitioning=!0;const s=`scroll${n[0].toUpperCase()+n.slice(1)}`;this._queueCallback((()=>{this._isTransitioning=!1,this._element.classList.remove(ht),this._element.classList.add(ct,lt),this._element.style[n]="",j.trigger(this._element,"shown.bs.collapse")}),this._element,!0),this._element.style[n]=`${this._element[s]}px`}hide(){if(this._isTransitioning||!this._isShown())return;if(j.trigger(this._element,"hide.bs.collapse").defaultPrevented)return;const t=this._getDimension();this._element.style[t]=`${this._element.getBoundingClientRect()[t]}px`,u(this._element),this._element.classList.add(ht),this._element.classList.remove(ct,lt);const e=this._triggerArray.length;for(let t=0;t{this._isTransitioning=!1,this._element.classList.remove(ht),this._element.classList.add(ct),j.trigger(this._element,"hidden.bs.collapse")}),this._element,!0)}_isShown(t=this._element){return t.classList.contains(lt)}_getConfig(t){return(t={...rt,...U.getDataAttributes(this._element),...t}).toggle=Boolean(t.toggle),t.parent=r(t.parent),a(ot,t,at),t}_getDimension(){return this._element.classList.contains("collapse-horizontal")?"width":"height"}_initializeChildren(){if(!this._config.parent)return;const t=V.find(ut,this._config.parent);V.find(ft,this._config.parent).filter((e=>!t.includes(e))).forEach((t=>{const e=n(t);e&&this._addAriaAndCollapsedClass([t],this._isShown(e))}))}_addAriaAndCollapsedClass(t,e){t.length&&t.forEach((t=>{e?t.classList.remove(dt):t.classList.add(dt),t.setAttribute("aria-expanded",e)}))}static jQueryInterface(t){return this.each((function(){const e={};"string"==typeof t&&/show|hide/.test(t)&&(e.toggle=!1);const i=pt.getOrCreateInstance(this,e);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t]()}}))}}j.on(document,"click.bs.collapse.data-api",ft,(function(t){("A"===t.target.tagName||t.delegateTarget&&"A"===t.delegateTarget.tagName)&&t.preventDefault();const e=i(this);V.find(e).forEach((t=>{pt.getOrCreateInstance(t,{toggle:!1}).toggle()}))})),g(pt);var mt="top",gt="bottom",_t="right",bt="left",vt="auto",yt=[mt,gt,_t,bt],wt="start",Et="end",At="clippingParents",Tt="viewport",Ot="popper",Ct="reference",kt=yt.reduce((function(t,e){return t.concat([e+"-"+wt,e+"-"+Et])}),[]),Lt=[].concat(yt,[vt]).reduce((function(t,e){return t.concat([e,e+"-"+wt,e+"-"+Et])}),[]),xt="beforeRead",Dt="read",St="afterRead",Nt="beforeMain",It="main",Pt="afterMain",jt="beforeWrite",Mt="write",Ht="afterWrite",Bt=[xt,Dt,St,Nt,It,Pt,jt,Mt,Ht];function Rt(t){return t?(t.nodeName||"").toLowerCase():null}function Wt(t){if(null==t)return window;if("[object Window]"!==t.toString()){var e=t.ownerDocument;return e&&e.defaultView||window}return t}function $t(t){return t instanceof Wt(t).Element||t instanceof Element}function zt(t){return t instanceof Wt(t).HTMLElement||t instanceof HTMLElement}function qt(t){return"undefined"!=typeof ShadowRoot&&(t instanceof Wt(t).ShadowRoot||t instanceof ShadowRoot)}const Ft={name:"applyStyles",enabled:!0,phase:"write",fn:function(t){var e=t.state;Object.keys(e.elements).forEach((function(t){var i=e.styles[t]||{},n=e.attributes[t]||{},s=e.elements[t];zt(s)&&Rt(s)&&(Object.assign(s.style,i),Object.keys(n).forEach((function(t){var e=n[t];!1===e?s.removeAttribute(t):s.setAttribute(t,!0===e?"":e)})))}))},effect:function(t){var e=t.state,i={popper:{position:e.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(e.elements.popper.style,i.popper),e.styles=i,e.elements.arrow&&Object.assign(e.elements.arrow.style,i.arrow),function(){Object.keys(e.elements).forEach((function(t){var n=e.elements[t],s=e.attributes[t]||{},o=Object.keys(e.styles.hasOwnProperty(t)?e.styles[t]:i[t]).reduce((function(t,e){return t[e]="",t}),{});zt(n)&&Rt(n)&&(Object.assign(n.style,o),Object.keys(s).forEach((function(t){n.removeAttribute(t)})))}))}},requires:["computeStyles"]};function Ut(t){return t.split("-")[0]}function Vt(t,e){var i=t.getBoundingClientRect();return{width:i.width/1,height:i.height/1,top:i.top/1,right:i.right/1,bottom:i.bottom/1,left:i.left/1,x:i.left/1,y:i.top/1}}function Kt(t){var e=Vt(t),i=t.offsetWidth,n=t.offsetHeight;return Math.abs(e.width-i)<=1&&(i=e.width),Math.abs(e.height-n)<=1&&(n=e.height),{x:t.offsetLeft,y:t.offsetTop,width:i,height:n}}function Xt(t,e){var i=e.getRootNode&&e.getRootNode();if(t.contains(e))return!0;if(i&&qt(i)){var n=e;do{if(n&&t.isSameNode(n))return!0;n=n.parentNode||n.host}while(n)}return!1}function Yt(t){return Wt(t).getComputedStyle(t)}function Qt(t){return["table","td","th"].indexOf(Rt(t))>=0}function Gt(t){return(($t(t)?t.ownerDocument:t.document)||window.document).documentElement}function Zt(t){return"html"===Rt(t)?t:t.assignedSlot||t.parentNode||(qt(t)?t.host:null)||Gt(t)}function Jt(t){return zt(t)&&"fixed"!==Yt(t).position?t.offsetParent:null}function te(t){for(var e=Wt(t),i=Jt(t);i&&Qt(i)&&"static"===Yt(i).position;)i=Jt(i);return i&&("html"===Rt(i)||"body"===Rt(i)&&"static"===Yt(i).position)?e:i||function(t){var e=-1!==navigator.userAgent.toLowerCase().indexOf("firefox");if(-1!==navigator.userAgent.indexOf("Trident")&&zt(t)&&"fixed"===Yt(t).position)return null;for(var i=Zt(t);zt(i)&&["html","body"].indexOf(Rt(i))<0;){var n=Yt(i);if("none"!==n.transform||"none"!==n.perspective||"paint"===n.contain||-1!==["transform","perspective"].indexOf(n.willChange)||e&&"filter"===n.willChange||e&&n.filter&&"none"!==n.filter)return i;i=i.parentNode}return null}(t)||e}function ee(t){return["top","bottom"].indexOf(t)>=0?"x":"y"}var ie=Math.max,ne=Math.min,se=Math.round;function oe(t,e,i){return ie(t,ne(e,i))}function re(t){return Object.assign({},{top:0,right:0,bottom:0,left:0},t)}function ae(t,e){return e.reduce((function(e,i){return e[i]=t,e}),{})}const le={name:"arrow",enabled:!0,phase:"main",fn:function(t){var e,i=t.state,n=t.name,s=t.options,o=i.elements.arrow,r=i.modifiersData.popperOffsets,a=Ut(i.placement),l=ee(a),c=[bt,_t].indexOf(a)>=0?"height":"width";if(o&&r){var h=function(t,e){return re("number"!=typeof(t="function"==typeof t?t(Object.assign({},e.rects,{placement:e.placement})):t)?t:ae(t,yt))}(s.padding,i),d=Kt(o),u="y"===l?mt:bt,f="y"===l?gt:_t,p=i.rects.reference[c]+i.rects.reference[l]-r[l]-i.rects.popper[c],m=r[l]-i.rects.reference[l],g=te(o),_=g?"y"===l?g.clientHeight||0:g.clientWidth||0:0,b=p/2-m/2,v=h[u],y=_-d[c]-h[f],w=_/2-d[c]/2+b,E=oe(v,w,y),A=l;i.modifiersData[n]=((e={})[A]=E,e.centerOffset=E-w,e)}},effect:function(t){var e=t.state,i=t.options.element,n=void 0===i?"[data-popper-arrow]":i;null!=n&&("string"!=typeof n||(n=e.elements.popper.querySelector(n)))&&Xt(e.elements.popper,n)&&(e.elements.arrow=n)},requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function ce(t){return t.split("-")[1]}var he={top:"auto",right:"auto",bottom:"auto",left:"auto"};function de(t){var e,i=t.popper,n=t.popperRect,s=t.placement,o=t.variation,r=t.offsets,a=t.position,l=t.gpuAcceleration,c=t.adaptive,h=t.roundOffsets,d=!0===h?function(t){var e=t.x,i=t.y,n=window.devicePixelRatio||1;return{x:se(se(e*n)/n)||0,y:se(se(i*n)/n)||0}}(r):"function"==typeof h?h(r):r,u=d.x,f=void 0===u?0:u,p=d.y,m=void 0===p?0:p,g=r.hasOwnProperty("x"),_=r.hasOwnProperty("y"),b=bt,v=mt,y=window;if(c){var w=te(i),E="clientHeight",A="clientWidth";w===Wt(i)&&"static"!==Yt(w=Gt(i)).position&&"absolute"===a&&(E="scrollHeight",A="scrollWidth"),w=w,s!==mt&&(s!==bt&&s!==_t||o!==Et)||(v=gt,m-=w[E]-n.height,m*=l?1:-1),s!==bt&&(s!==mt&&s!==gt||o!==Et)||(b=_t,f-=w[A]-n.width,f*=l?1:-1)}var T,O=Object.assign({position:a},c&&he);return l?Object.assign({},O,((T={})[v]=_?"0":"",T[b]=g?"0":"",T.transform=(y.devicePixelRatio||1)<=1?"translate("+f+"px, "+m+"px)":"translate3d("+f+"px, "+m+"px, 0)",T)):Object.assign({},O,((e={})[v]=_?m+"px":"",e[b]=g?f+"px":"",e.transform="",e))}const ue={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:function(t){var e=t.state,i=t.options,n=i.gpuAcceleration,s=void 0===n||n,o=i.adaptive,r=void 0===o||o,a=i.roundOffsets,l=void 0===a||a,c={placement:Ut(e.placement),variation:ce(e.placement),popper:e.elements.popper,popperRect:e.rects.popper,gpuAcceleration:s};null!=e.modifiersData.popperOffsets&&(e.styles.popper=Object.assign({},e.styles.popper,de(Object.assign({},c,{offsets:e.modifiersData.popperOffsets,position:e.options.strategy,adaptive:r,roundOffsets:l})))),null!=e.modifiersData.arrow&&(e.styles.arrow=Object.assign({},e.styles.arrow,de(Object.assign({},c,{offsets:e.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:l})))),e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-placement":e.placement})},data:{}};var fe={passive:!0};const pe={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:function(t){var e=t.state,i=t.instance,n=t.options,s=n.scroll,o=void 0===s||s,r=n.resize,a=void 0===r||r,l=Wt(e.elements.popper),c=[].concat(e.scrollParents.reference,e.scrollParents.popper);return o&&c.forEach((function(t){t.addEventListener("scroll",i.update,fe)})),a&&l.addEventListener("resize",i.update,fe),function(){o&&c.forEach((function(t){t.removeEventListener("scroll",i.update,fe)})),a&&l.removeEventListener("resize",i.update,fe)}},data:{}};var me={left:"right",right:"left",bottom:"top",top:"bottom"};function ge(t){return t.replace(/left|right|bottom|top/g,(function(t){return me[t]}))}var _e={start:"end",end:"start"};function be(t){return t.replace(/start|end/g,(function(t){return _e[t]}))}function ve(t){var e=Wt(t);return{scrollLeft:e.pageXOffset,scrollTop:e.pageYOffset}}function ye(t){return Vt(Gt(t)).left+ve(t).scrollLeft}function we(t){var e=Yt(t),i=e.overflow,n=e.overflowX,s=e.overflowY;return/auto|scroll|overlay|hidden/.test(i+s+n)}function Ee(t){return["html","body","#document"].indexOf(Rt(t))>=0?t.ownerDocument.body:zt(t)&&we(t)?t:Ee(Zt(t))}function Ae(t,e){var i;void 0===e&&(e=[]);var n=Ee(t),s=n===(null==(i=t.ownerDocument)?void 0:i.body),o=Wt(n),r=s?[o].concat(o.visualViewport||[],we(n)?n:[]):n,a=e.concat(r);return s?a:a.concat(Ae(Zt(r)))}function Te(t){return Object.assign({},t,{left:t.x,top:t.y,right:t.x+t.width,bottom:t.y+t.height})}function Oe(t,e){return e===Tt?Te(function(t){var e=Wt(t),i=Gt(t),n=e.visualViewport,s=i.clientWidth,o=i.clientHeight,r=0,a=0;return n&&(s=n.width,o=n.height,/^((?!chrome|android).)*safari/i.test(navigator.userAgent)||(r=n.offsetLeft,a=n.offsetTop)),{width:s,height:o,x:r+ye(t),y:a}}(t)):zt(e)?function(t){var e=Vt(t);return e.top=e.top+t.clientTop,e.left=e.left+t.clientLeft,e.bottom=e.top+t.clientHeight,e.right=e.left+t.clientWidth,e.width=t.clientWidth,e.height=t.clientHeight,e.x=e.left,e.y=e.top,e}(e):Te(function(t){var e,i=Gt(t),n=ve(t),s=null==(e=t.ownerDocument)?void 0:e.body,o=ie(i.scrollWidth,i.clientWidth,s?s.scrollWidth:0,s?s.clientWidth:0),r=ie(i.scrollHeight,i.clientHeight,s?s.scrollHeight:0,s?s.clientHeight:0),a=-n.scrollLeft+ye(t),l=-n.scrollTop;return"rtl"===Yt(s||i).direction&&(a+=ie(i.clientWidth,s?s.clientWidth:0)-o),{width:o,height:r,x:a,y:l}}(Gt(t)))}function Ce(t){var e,i=t.reference,n=t.element,s=t.placement,o=s?Ut(s):null,r=s?ce(s):null,a=i.x+i.width/2-n.width/2,l=i.y+i.height/2-n.height/2;switch(o){case mt:e={x:a,y:i.y-n.height};break;case gt:e={x:a,y:i.y+i.height};break;case _t:e={x:i.x+i.width,y:l};break;case bt:e={x:i.x-n.width,y:l};break;default:e={x:i.x,y:i.y}}var c=o?ee(o):null;if(null!=c){var h="y"===c?"height":"width";switch(r){case wt:e[c]=e[c]-(i[h]/2-n[h]/2);break;case Et:e[c]=e[c]+(i[h]/2-n[h]/2)}}return e}function ke(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=void 0===n?t.placement:n,o=i.boundary,r=void 0===o?At:o,a=i.rootBoundary,l=void 0===a?Tt:a,c=i.elementContext,h=void 0===c?Ot:c,d=i.altBoundary,u=void 0!==d&&d,f=i.padding,p=void 0===f?0:f,m=re("number"!=typeof p?p:ae(p,yt)),g=h===Ot?Ct:Ot,_=t.rects.popper,b=t.elements[u?g:h],v=function(t,e,i){var n="clippingParents"===e?function(t){var e=Ae(Zt(t)),i=["absolute","fixed"].indexOf(Yt(t).position)>=0&&zt(t)?te(t):t;return $t(i)?e.filter((function(t){return $t(t)&&Xt(t,i)&&"body"!==Rt(t)})):[]}(t):[].concat(e),s=[].concat(n,[i]),o=s[0],r=s.reduce((function(e,i){var n=Oe(t,i);return e.top=ie(n.top,e.top),e.right=ne(n.right,e.right),e.bottom=ne(n.bottom,e.bottom),e.left=ie(n.left,e.left),e}),Oe(t,o));return r.width=r.right-r.left,r.height=r.bottom-r.top,r.x=r.left,r.y=r.top,r}($t(b)?b:b.contextElement||Gt(t.elements.popper),r,l),y=Vt(t.elements.reference),w=Ce({reference:y,element:_,strategy:"absolute",placement:s}),E=Te(Object.assign({},_,w)),A=h===Ot?E:y,T={top:v.top-A.top+m.top,bottom:A.bottom-v.bottom+m.bottom,left:v.left-A.left+m.left,right:A.right-v.right+m.right},O=t.modifiersData.offset;if(h===Ot&&O){var C=O[s];Object.keys(T).forEach((function(t){var e=[_t,gt].indexOf(t)>=0?1:-1,i=[mt,gt].indexOf(t)>=0?"y":"x";T[t]+=C[i]*e}))}return T}function Le(t,e){void 0===e&&(e={});var i=e,n=i.placement,s=i.boundary,o=i.rootBoundary,r=i.padding,a=i.flipVariations,l=i.allowedAutoPlacements,c=void 0===l?Lt:l,h=ce(n),d=h?a?kt:kt.filter((function(t){return ce(t)===h})):yt,u=d.filter((function(t){return c.indexOf(t)>=0}));0===u.length&&(u=d);var f=u.reduce((function(e,i){return e[i]=ke(t,{placement:i,boundary:s,rootBoundary:o,padding:r})[Ut(i)],e}),{});return Object.keys(f).sort((function(t,e){return f[t]-f[e]}))}const xe={name:"flip",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name;if(!e.modifiersData[n]._skip){for(var s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0===r||r,l=i.fallbackPlacements,c=i.padding,h=i.boundary,d=i.rootBoundary,u=i.altBoundary,f=i.flipVariations,p=void 0===f||f,m=i.allowedAutoPlacements,g=e.options.placement,_=Ut(g),b=l||(_!==g&&p?function(t){if(Ut(t)===vt)return[];var e=ge(t);return[be(t),e,be(e)]}(g):[ge(g)]),v=[g].concat(b).reduce((function(t,i){return t.concat(Ut(i)===vt?Le(e,{placement:i,boundary:h,rootBoundary:d,padding:c,flipVariations:p,allowedAutoPlacements:m}):i)}),[]),y=e.rects.reference,w=e.rects.popper,E=new Map,A=!0,T=v[0],O=0;O=0,D=x?"width":"height",S=ke(e,{placement:C,boundary:h,rootBoundary:d,altBoundary:u,padding:c}),N=x?L?_t:bt:L?gt:mt;y[D]>w[D]&&(N=ge(N));var I=ge(N),P=[];if(o&&P.push(S[k]<=0),a&&P.push(S[N]<=0,S[I]<=0),P.every((function(t){return t}))){T=C,A=!1;break}E.set(C,P)}if(A)for(var j=function(t){var e=v.find((function(e){var i=E.get(e);if(i)return i.slice(0,t).every((function(t){return t}))}));if(e)return T=e,"break"},M=p?3:1;M>0&&"break"!==j(M);M--);e.placement!==T&&(e.modifiersData[n]._skip=!0,e.placement=T,e.reset=!0)}},requiresIfExists:["offset"],data:{_skip:!1}};function De(t,e,i){return void 0===i&&(i={x:0,y:0}),{top:t.top-e.height-i.y,right:t.right-e.width+i.x,bottom:t.bottom-e.height+i.y,left:t.left-e.width-i.x}}function Se(t){return[mt,_t,gt,bt].some((function(e){return t[e]>=0}))}const Ne={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:function(t){var e=t.state,i=t.name,n=e.rects.reference,s=e.rects.popper,o=e.modifiersData.preventOverflow,r=ke(e,{elementContext:"reference"}),a=ke(e,{altBoundary:!0}),l=De(r,n),c=De(a,s,o),h=Se(l),d=Se(c);e.modifiersData[i]={referenceClippingOffsets:l,popperEscapeOffsets:c,isReferenceHidden:h,hasPopperEscaped:d},e.attributes.popper=Object.assign({},e.attributes.popper,{"data-popper-reference-hidden":h,"data-popper-escaped":d})}},Ie={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.offset,o=void 0===s?[0,0]:s,r=Lt.reduce((function(t,i){return t[i]=function(t,e,i){var n=Ut(t),s=[bt,mt].indexOf(n)>=0?-1:1,o="function"==typeof i?i(Object.assign({},e,{placement:t})):i,r=o[0],a=o[1];return r=r||0,a=(a||0)*s,[bt,_t].indexOf(n)>=0?{x:a,y:r}:{x:r,y:a}}(i,e.rects,o),t}),{}),a=r[e.placement],l=a.x,c=a.y;null!=e.modifiersData.popperOffsets&&(e.modifiersData.popperOffsets.x+=l,e.modifiersData.popperOffsets.y+=c),e.modifiersData[n]=r}},Pe={name:"popperOffsets",enabled:!0,phase:"read",fn:function(t){var e=t.state,i=t.name;e.modifiersData[i]=Ce({reference:e.rects.reference,element:e.rects.popper,strategy:"absolute",placement:e.placement})},data:{}},je={name:"preventOverflow",enabled:!0,phase:"main",fn:function(t){var e=t.state,i=t.options,n=t.name,s=i.mainAxis,o=void 0===s||s,r=i.altAxis,a=void 0!==r&&r,l=i.boundary,c=i.rootBoundary,h=i.altBoundary,d=i.padding,u=i.tether,f=void 0===u||u,p=i.tetherOffset,m=void 0===p?0:p,g=ke(e,{boundary:l,rootBoundary:c,padding:d,altBoundary:h}),_=Ut(e.placement),b=ce(e.placement),v=!b,y=ee(_),w="x"===y?"y":"x",E=e.modifiersData.popperOffsets,A=e.rects.reference,T=e.rects.popper,O="function"==typeof m?m(Object.assign({},e.rects,{placement:e.placement})):m,C={x:0,y:0};if(E){if(o||a){var k="y"===y?mt:bt,L="y"===y?gt:_t,x="y"===y?"height":"width",D=E[y],S=E[y]+g[k],N=E[y]-g[L],I=f?-T[x]/2:0,P=b===wt?A[x]:T[x],j=b===wt?-T[x]:-A[x],M=e.elements.arrow,H=f&&M?Kt(M):{width:0,height:0},B=e.modifiersData["arrow#persistent"]?e.modifiersData["arrow#persistent"].padding:{top:0,right:0,bottom:0,left:0},R=B[k],W=B[L],$=oe(0,A[x],H[x]),z=v?A[x]/2-I-$-R-O:P-$-R-O,q=v?-A[x]/2+I+$+W+O:j+$+W+O,F=e.elements.arrow&&te(e.elements.arrow),U=F?"y"===y?F.clientTop||0:F.clientLeft||0:0,V=e.modifiersData.offset?e.modifiersData.offset[e.placement][y]:0,K=E[y]+z-V-U,X=E[y]+q-V;if(o){var Y=oe(f?ne(S,K):S,D,f?ie(N,X):N);E[y]=Y,C[y]=Y-D}if(a){var Q="x"===y?mt:bt,G="x"===y?gt:_t,Z=E[w],J=Z+g[Q],tt=Z-g[G],et=oe(f?ne(J,K):J,Z,f?ie(tt,X):tt);E[w]=et,C[w]=et-Z}}e.modifiersData[n]=C}},requiresIfExists:["offset"]};function Me(t,e,i){void 0===i&&(i=!1);var n=zt(e);zt(e)&&function(t){var e=t.getBoundingClientRect();e.width,t.offsetWidth,e.height,t.offsetHeight}(e);var s,o,r=Gt(e),a=Vt(t),l={scrollLeft:0,scrollTop:0},c={x:0,y:0};return(n||!n&&!i)&&(("body"!==Rt(e)||we(r))&&(l=(s=e)!==Wt(s)&&zt(s)?{scrollLeft:(o=s).scrollLeft,scrollTop:o.scrollTop}:ve(s)),zt(e)?((c=Vt(e)).x+=e.clientLeft,c.y+=e.clientTop):r&&(c.x=ye(r))),{x:a.left+l.scrollLeft-c.x,y:a.top+l.scrollTop-c.y,width:a.width,height:a.height}}function He(t){var e=new Map,i=new Set,n=[];function s(t){i.add(t.name),[].concat(t.requires||[],t.requiresIfExists||[]).forEach((function(t){if(!i.has(t)){var n=e.get(t);n&&s(n)}})),n.push(t)}return t.forEach((function(t){e.set(t.name,t)})),t.forEach((function(t){i.has(t.name)||s(t)})),n}var Be={placement:"bottom",modifiers:[],strategy:"absolute"};function Re(){for(var t=arguments.length,e=new Array(t),i=0;ij.on(t,"mouseover",d))),this._element.focus(),this._element.setAttribute("aria-expanded",!0),this._menu.classList.add(Je),this._element.classList.add(Je),j.trigger(this._element,"shown.bs.dropdown",t)}hide(){if(c(this._element)||!this._isShown(this._menu))return;const t={relatedTarget:this._element};this._completeHide(t)}dispose(){this._popper&&this._popper.destroy(),super.dispose()}update(){this._inNavbar=this._detectNavbar(),this._popper&&this._popper.update()}_completeHide(t){j.trigger(this._element,"hide.bs.dropdown",t).defaultPrevented||("ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>j.off(t,"mouseover",d))),this._popper&&this._popper.destroy(),this._menu.classList.remove(Je),this._element.classList.remove(Je),this._element.setAttribute("aria-expanded","false"),U.removeDataAttribute(this._menu,"popper"),j.trigger(this._element,"hidden.bs.dropdown",t))}_getConfig(t){if(t={...this.constructor.Default,...U.getDataAttributes(this._element),...t},a(Ue,t,this.constructor.DefaultType),"object"==typeof t.reference&&!o(t.reference)&&"function"!=typeof t.reference.getBoundingClientRect)throw new TypeError(`${Ue.toUpperCase()}: Option "reference" provided type "object" without a required "getBoundingClientRect" method.`);return t}_createPopper(t){if(void 0===Fe)throw new TypeError("Bootstrap's dropdowns require Popper (https://popper.js.org)");let e=this._element;"parent"===this._config.reference?e=t:o(this._config.reference)?e=r(this._config.reference):"object"==typeof this._config.reference&&(e=this._config.reference);const i=this._getPopperConfig(),n=i.modifiers.find((t=>"applyStyles"===t.name&&!1===t.enabled));this._popper=qe(e,this._menu,i),n&&U.setDataAttribute(this._menu,"popper","static")}_isShown(t=this._element){return t.classList.contains(Je)}_getMenuElement(){return V.next(this._element,ei)[0]}_getPlacement(){const t=this._element.parentNode;if(t.classList.contains("dropend"))return ri;if(t.classList.contains("dropstart"))return ai;const e="end"===getComputedStyle(this._menu).getPropertyValue("--bs-position").trim();return t.classList.contains("dropup")?e?ni:ii:e?oi:si}_detectNavbar(){return null!==this._element.closest(".navbar")}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_getPopperConfig(){const t={placement:this._getPlacement(),modifiers:[{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"offset",options:{offset:this._getOffset()}}]};return"static"===this._config.display&&(t.modifiers=[{name:"applyStyles",enabled:!1}]),{...t,..."function"==typeof this._config.popperConfig?this._config.popperConfig(t):this._config.popperConfig}}_selectMenuItem({key:t,target:e}){const i=V.find(".dropdown-menu .dropdown-item:not(.disabled):not(:disabled)",this._menu).filter(l);i.length&&v(i,e,t===Ye,!i.includes(e)).focus()}static jQueryInterface(t){return this.each((function(){const e=hi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}static clearMenus(t){if(t&&(2===t.button||"keyup"===t.type&&"Tab"!==t.key))return;const e=V.find(ti);for(let i=0,n=e.length;ie+t)),this._setElementAttributes(di,"paddingRight",(e=>e+t)),this._setElementAttributes(ui,"marginRight",(e=>e-t))}_disableOverFlow(){this._saveInitialAttribute(this._element,"overflow"),this._element.style.overflow="hidden"}_setElementAttributes(t,e,i){const n=this.getWidth();this._applyManipulationCallback(t,(t=>{if(t!==this._element&&window.innerWidth>t.clientWidth+n)return;this._saveInitialAttribute(t,e);const s=window.getComputedStyle(t)[e];t.style[e]=`${i(Number.parseFloat(s))}px`}))}reset(){this._resetElementAttributes(this._element,"overflow"),this._resetElementAttributes(this._element,"paddingRight"),this._resetElementAttributes(di,"paddingRight"),this._resetElementAttributes(ui,"marginRight")}_saveInitialAttribute(t,e){const i=t.style[e];i&&U.setDataAttribute(t,e,i)}_resetElementAttributes(t,e){this._applyManipulationCallback(t,(t=>{const i=U.getDataAttribute(t,e);void 0===i?t.style.removeProperty(e):(U.removeDataAttribute(t,e),t.style[e]=i)}))}_applyManipulationCallback(t,e){o(t)?e(t):V.find(t,this._element).forEach(e)}isOverflowing(){return this.getWidth()>0}}const pi={className:"modal-backdrop",isVisible:!0,isAnimated:!1,rootElement:"body",clickCallback:null},mi={className:"string",isVisible:"boolean",isAnimated:"boolean",rootElement:"(element|string)",clickCallback:"(function|null)"},gi="show",_i="mousedown.bs.backdrop";class bi{constructor(t){this._config=this._getConfig(t),this._isAppended=!1,this._element=null}show(t){this._config.isVisible?(this._append(),this._config.isAnimated&&u(this._getElement()),this._getElement().classList.add(gi),this._emulateAnimation((()=>{_(t)}))):_(t)}hide(t){this._config.isVisible?(this._getElement().classList.remove(gi),this._emulateAnimation((()=>{this.dispose(),_(t)}))):_(t)}_getElement(){if(!this._element){const t=document.createElement("div");t.className=this._config.className,this._config.isAnimated&&t.classList.add("fade"),this._element=t}return this._element}_getConfig(t){return(t={...pi,..."object"==typeof t?t:{}}).rootElement=r(t.rootElement),a("backdrop",t,mi),t}_append(){this._isAppended||(this._config.rootElement.append(this._getElement()),j.on(this._getElement(),_i,(()=>{_(this._config.clickCallback)})),this._isAppended=!0)}dispose(){this._isAppended&&(j.off(this._element,_i),this._element.remove(),this._isAppended=!1)}_emulateAnimation(t){b(t,this._getElement(),this._config.isAnimated)}}const vi={trapElement:null,autofocus:!0},yi={trapElement:"element",autofocus:"boolean"},wi=".bs.focustrap",Ei="backward";class Ai{constructor(t){this._config=this._getConfig(t),this._isActive=!1,this._lastTabNavDirection=null}activate(){const{trapElement:t,autofocus:e}=this._config;this._isActive||(e&&t.focus(),j.off(document,wi),j.on(document,"focusin.bs.focustrap",(t=>this._handleFocusin(t))),j.on(document,"keydown.tab.bs.focustrap",(t=>this._handleKeydown(t))),this._isActive=!0)}deactivate(){this._isActive&&(this._isActive=!1,j.off(document,wi))}_handleFocusin(t){const{target:e}=t,{trapElement:i}=this._config;if(e===document||e===i||i.contains(e))return;const n=V.focusableChildren(i);0===n.length?i.focus():this._lastTabNavDirection===Ei?n[n.length-1].focus():n[0].focus()}_handleKeydown(t){"Tab"===t.key&&(this._lastTabNavDirection=t.shiftKey?Ei:"forward")}_getConfig(t){return t={...vi,..."object"==typeof t?t:{}},a("focustrap",t,yi),t}}const Ti="modal",Oi="Escape",Ci={backdrop:!0,keyboard:!0,focus:!0},ki={backdrop:"(boolean|string)",keyboard:"boolean",focus:"boolean"},Li="hidden.bs.modal",xi="show.bs.modal",Di="resize.bs.modal",Si="click.dismiss.bs.modal",Ni="keydown.dismiss.bs.modal",Ii="mousedown.dismiss.bs.modal",Pi="modal-open",ji="show",Mi="modal-static";class Hi extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._dialog=V.findOne(".modal-dialog",this._element),this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._isShown=!1,this._ignoreBackdropClick=!1,this._isTransitioning=!1,this._scrollBar=new fi}static get Default(){return Ci}static get NAME(){return Ti}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||this._isTransitioning||j.trigger(this._element,xi,{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._isAnimated()&&(this._isTransitioning=!0),this._scrollBar.hide(),document.body.classList.add(Pi),this._adjustDialog(),this._setEscapeEvent(),this._setResizeEvent(),j.on(this._dialog,Ii,(()=>{j.one(this._element,"mouseup.dismiss.bs.modal",(t=>{t.target===this._element&&(this._ignoreBackdropClick=!0)}))})),this._showBackdrop((()=>this._showElement(t))))}hide(){if(!this._isShown||this._isTransitioning)return;if(j.trigger(this._element,"hide.bs.modal").defaultPrevented)return;this._isShown=!1;const t=this._isAnimated();t&&(this._isTransitioning=!0),this._setEscapeEvent(),this._setResizeEvent(),this._focustrap.deactivate(),this._element.classList.remove(ji),j.off(this._element,Si),j.off(this._dialog,Ii),this._queueCallback((()=>this._hideModal()),this._element,t)}dispose(){[window,this._dialog].forEach((t=>j.off(t,".bs.modal"))),this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}handleUpdate(){this._adjustDialog()}_initializeBackDrop(){return new bi({isVisible:Boolean(this._config.backdrop),isAnimated:this._isAnimated()})}_initializeFocusTrap(){return new Ai({trapElement:this._element})}_getConfig(t){return t={...Ci,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(Ti,t,ki),t}_showElement(t){const e=this._isAnimated(),i=V.findOne(".modal-body",this._dialog);this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE||document.body.append(this._element),this._element.style.display="block",this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.scrollTop=0,i&&(i.scrollTop=0),e&&u(this._element),this._element.classList.add(ji),this._queueCallback((()=>{this._config.focus&&this._focustrap.activate(),this._isTransitioning=!1,j.trigger(this._element,"shown.bs.modal",{relatedTarget:t})}),this._dialog,e)}_setEscapeEvent(){this._isShown?j.on(this._element,Ni,(t=>{this._config.keyboard&&t.key===Oi?(t.preventDefault(),this.hide()):this._config.keyboard||t.key!==Oi||this._triggerBackdropTransition()})):j.off(this._element,Ni)}_setResizeEvent(){this._isShown?j.on(window,Di,(()=>this._adjustDialog())):j.off(window,Di)}_hideModal(){this._element.style.display="none",this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._isTransitioning=!1,this._backdrop.hide((()=>{document.body.classList.remove(Pi),this._resetAdjustments(),this._scrollBar.reset(),j.trigger(this._element,Li)}))}_showBackdrop(t){j.on(this._element,Si,(t=>{this._ignoreBackdropClick?this._ignoreBackdropClick=!1:t.target===t.currentTarget&&(!0===this._config.backdrop?this.hide():"static"===this._config.backdrop&&this._triggerBackdropTransition())})),this._backdrop.show(t)}_isAnimated(){return this._element.classList.contains("fade")}_triggerBackdropTransition(){if(j.trigger(this._element,"hidePrevented.bs.modal").defaultPrevented)return;const{classList:t,scrollHeight:e,style:i}=this._element,n=e>document.documentElement.clientHeight;!n&&"hidden"===i.overflowY||t.contains(Mi)||(n||(i.overflowY="hidden"),t.add(Mi),this._queueCallback((()=>{t.remove(Mi),n||this._queueCallback((()=>{i.overflowY=""}),this._dialog)}),this._dialog),this._element.focus())}_adjustDialog(){const t=this._element.scrollHeight>document.documentElement.clientHeight,e=this._scrollBar.getWidth(),i=e>0;(!i&&t&&!m()||i&&!t&&m())&&(this._element.style.paddingLeft=`${e}px`),(i&&!t&&!m()||!i&&t&&m())&&(this._element.style.paddingRight=`${e}px`)}_resetAdjustments(){this._element.style.paddingLeft="",this._element.style.paddingRight=""}static jQueryInterface(t,e){return this.each((function(){const i=Hi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===i[t])throw new TypeError(`No method named "${t}"`);i[t](e)}}))}}j.on(document,"click.bs.modal.data-api",'[data-bs-toggle="modal"]',(function(t){const e=n(this);["A","AREA"].includes(this.tagName)&&t.preventDefault(),j.one(e,xi,(t=>{t.defaultPrevented||j.one(e,Li,(()=>{l(this)&&this.focus()}))}));const i=V.findOne(".modal.show");i&&Hi.getInstance(i).hide(),Hi.getOrCreateInstance(e).toggle(this)})),R(Hi),g(Hi);const Bi="offcanvas",Ri={backdrop:!0,keyboard:!0,scroll:!1},Wi={backdrop:"boolean",keyboard:"boolean",scroll:"boolean"},$i="show",zi=".offcanvas.show",qi="hidden.bs.offcanvas";class Fi extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._isShown=!1,this._backdrop=this._initializeBackDrop(),this._focustrap=this._initializeFocusTrap(),this._addEventListeners()}static get NAME(){return Bi}static get Default(){return Ri}toggle(t){return this._isShown?this.hide():this.show(t)}show(t){this._isShown||j.trigger(this._element,"show.bs.offcanvas",{relatedTarget:t}).defaultPrevented||(this._isShown=!0,this._element.style.visibility="visible",this._backdrop.show(),this._config.scroll||(new fi).hide(),this._element.removeAttribute("aria-hidden"),this._element.setAttribute("aria-modal",!0),this._element.setAttribute("role","dialog"),this._element.classList.add($i),this._queueCallback((()=>{this._config.scroll||this._focustrap.activate(),j.trigger(this._element,"shown.bs.offcanvas",{relatedTarget:t})}),this._element,!0))}hide(){this._isShown&&(j.trigger(this._element,"hide.bs.offcanvas").defaultPrevented||(this._focustrap.deactivate(),this._element.blur(),this._isShown=!1,this._element.classList.remove($i),this._backdrop.hide(),this._queueCallback((()=>{this._element.setAttribute("aria-hidden",!0),this._element.removeAttribute("aria-modal"),this._element.removeAttribute("role"),this._element.style.visibility="hidden",this._config.scroll||(new fi).reset(),j.trigger(this._element,qi)}),this._element,!0)))}dispose(){this._backdrop.dispose(),this._focustrap.deactivate(),super.dispose()}_getConfig(t){return t={...Ri,...U.getDataAttributes(this._element),..."object"==typeof t?t:{}},a(Bi,t,Wi),t}_initializeBackDrop(){return new bi({className:"offcanvas-backdrop",isVisible:this._config.backdrop,isAnimated:!0,rootElement:this._element.parentNode,clickCallback:()=>this.hide()})}_initializeFocusTrap(){return new Ai({trapElement:this._element})}_addEventListeners(){j.on(this._element,"keydown.dismiss.bs.offcanvas",(t=>{this._config.keyboard&&"Escape"===t.key&&this.hide()}))}static jQueryInterface(t){return this.each((function(){const e=Fi.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t]||t.startsWith("_")||"constructor"===t)throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}j.on(document,"click.bs.offcanvas.data-api",'[data-bs-toggle="offcanvas"]',(function(t){const e=n(this);if(["A","AREA"].includes(this.tagName)&&t.preventDefault(),c(this))return;j.one(e,qi,(()=>{l(this)&&this.focus()}));const i=V.findOne(zi);i&&i!==e&&Fi.getInstance(i).hide(),Fi.getOrCreateInstance(e).toggle(this)})),j.on(window,"load.bs.offcanvas.data-api",(()=>V.find(zi).forEach((t=>Fi.getOrCreateInstance(t).show())))),R(Fi),g(Fi);const Ui=new Set(["background","cite","href","itemtype","longdesc","poster","src","xlink:href"]),Vi=/^(?:(?:https?|mailto|ftp|tel|file|sms):|[^#&/:?]*(?:[#/?]|$))/i,Ki=/^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[\d+/a-z]+=*$/i,Xi=(t,e)=>{const i=t.nodeName.toLowerCase();if(e.includes(i))return!Ui.has(i)||Boolean(Vi.test(t.nodeValue)||Ki.test(t.nodeValue));const n=e.filter((t=>t instanceof RegExp));for(let t=0,e=n.length;t{Xi(t,r)||i.removeAttribute(t.nodeName)}))}return n.body.innerHTML}const Qi="tooltip",Gi=new Set(["sanitize","allowList","sanitizeFn"]),Zi={animation:"boolean",template:"string",title:"(string|element|function)",trigger:"string",delay:"(number|object)",html:"boolean",selector:"(string|boolean)",placement:"(string|function)",offset:"(array|string|function)",container:"(string|element|boolean)",fallbackPlacements:"array",boundary:"(string|element)",customClass:"(string|function)",sanitize:"boolean",sanitizeFn:"(null|function)",allowList:"object",popperConfig:"(null|object|function)"},Ji={AUTO:"auto",TOP:"top",RIGHT:m()?"left":"right",BOTTOM:"bottom",LEFT:m()?"right":"left"},tn={animation:!0,template:'',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:[0,0],container:!1,fallbackPlacements:["top","right","bottom","left"],boundary:"clippingParents",customClass:"",sanitize:!0,sanitizeFn:null,allowList:{"*":["class","dir","id","lang","role",/^aria-[\w-]*$/i],a:["target","href","title","rel"],area:[],b:[],br:[],col:[],code:[],div:[],em:[],hr:[],h1:[],h2:[],h3:[],h4:[],h5:[],h6:[],i:[],img:["src","srcset","alt","title","width","height"],li:[],ol:[],p:[],pre:[],s:[],small:[],span:[],sub:[],sup:[],strong:[],u:[],ul:[]},popperConfig:null},en={HIDE:"hide.bs.tooltip",HIDDEN:"hidden.bs.tooltip",SHOW:"show.bs.tooltip",SHOWN:"shown.bs.tooltip",INSERTED:"inserted.bs.tooltip",CLICK:"click.bs.tooltip",FOCUSIN:"focusin.bs.tooltip",FOCUSOUT:"focusout.bs.tooltip",MOUSEENTER:"mouseenter.bs.tooltip",MOUSELEAVE:"mouseleave.bs.tooltip"},nn="fade",sn="show",on="show",rn="out",an=".tooltip-inner",ln=".modal",cn="hide.bs.modal",hn="hover",dn="focus";class un extends B{constructor(t,e){if(void 0===Fe)throw new TypeError("Bootstrap's tooltips require Popper (https://popper.js.org)");super(t),this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this._config=this._getConfig(e),this.tip=null,this._setListeners()}static get Default(){return tn}static get NAME(){return Qi}static get Event(){return en}static get DefaultType(){return Zi}enable(){this._isEnabled=!0}disable(){this._isEnabled=!1}toggleEnabled(){this._isEnabled=!this._isEnabled}toggle(t){if(this._isEnabled)if(t){const e=this._initializeOnDelegatedTarget(t);e._activeTrigger.click=!e._activeTrigger.click,e._isWithActiveTrigger()?e._enter(null,e):e._leave(null,e)}else{if(this.getTipElement().classList.contains(sn))return void this._leave(null,this);this._enter(null,this)}}dispose(){clearTimeout(this._timeout),j.off(this._element.closest(ln),cn,this._hideModalHandler),this.tip&&this.tip.remove(),this._disposePopper(),super.dispose()}show(){if("none"===this._element.style.display)throw new Error("Please use show on visible elements");if(!this.isWithContent()||!this._isEnabled)return;const t=j.trigger(this._element,this.constructor.Event.SHOW),e=h(this._element),i=null===e?this._element.ownerDocument.documentElement.contains(this._element):e.contains(this._element);if(t.defaultPrevented||!i)return;"tooltip"===this.constructor.NAME&&this.tip&&this.getTitle()!==this.tip.querySelector(an).innerHTML&&(this._disposePopper(),this.tip.remove(),this.tip=null);const n=this.getTipElement(),s=(t=>{do{t+=Math.floor(1e6*Math.random())}while(document.getElementById(t));return t})(this.constructor.NAME);n.setAttribute("id",s),this._element.setAttribute("aria-describedby",s),this._config.animation&&n.classList.add(nn);const o="function"==typeof this._config.placement?this._config.placement.call(this,n,this._element):this._config.placement,r=this._getAttachment(o);this._addAttachmentClass(r);const{container:a}=this._config;H.set(n,this.constructor.DATA_KEY,this),this._element.ownerDocument.documentElement.contains(this.tip)||(a.append(n),j.trigger(this._element,this.constructor.Event.INSERTED)),this._popper?this._popper.update():this._popper=qe(this._element,n,this._getPopperConfig(r)),n.classList.add(sn);const l=this._resolvePossibleFunction(this._config.customClass);l&&n.classList.add(...l.split(" ")),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>{j.on(t,"mouseover",d)}));const c=this.tip.classList.contains(nn);this._queueCallback((()=>{const t=this._hoverState;this._hoverState=null,j.trigger(this._element,this.constructor.Event.SHOWN),t===rn&&this._leave(null,this)}),this.tip,c)}hide(){if(!this._popper)return;const t=this.getTipElement();if(j.trigger(this._element,this.constructor.Event.HIDE).defaultPrevented)return;t.classList.remove(sn),"ontouchstart"in document.documentElement&&[].concat(...document.body.children).forEach((t=>j.off(t,"mouseover",d))),this._activeTrigger.click=!1,this._activeTrigger.focus=!1,this._activeTrigger.hover=!1;const e=this.tip.classList.contains(nn);this._queueCallback((()=>{this._isWithActiveTrigger()||(this._hoverState!==on&&t.remove(),this._cleanTipClass(),this._element.removeAttribute("aria-describedby"),j.trigger(this._element,this.constructor.Event.HIDDEN),this._disposePopper())}),this.tip,e),this._hoverState=""}update(){null!==this._popper&&this._popper.update()}isWithContent(){return Boolean(this.getTitle())}getTipElement(){if(this.tip)return this.tip;const t=document.createElement("div");t.innerHTML=this._config.template;const e=t.children[0];return this.setContent(e),e.classList.remove(nn,sn),this.tip=e,this.tip}setContent(t){this._sanitizeAndSetContent(t,this.getTitle(),an)}_sanitizeAndSetContent(t,e,i){const n=V.findOne(i,t);e||!n?this.setElementContent(n,e):n.remove()}setElementContent(t,e){if(null!==t)return o(e)?(e=r(e),void(this._config.html?e.parentNode!==t&&(t.innerHTML="",t.append(e)):t.textContent=e.textContent)):void(this._config.html?(this._config.sanitize&&(e=Yi(e,this._config.allowList,this._config.sanitizeFn)),t.innerHTML=e):t.textContent=e)}getTitle(){const t=this._element.getAttribute("data-bs-original-title")||this._config.title;return this._resolvePossibleFunction(t)}updateAttachment(t){return"right"===t?"end":"left"===t?"start":t}_initializeOnDelegatedTarget(t,e){return e||this.constructor.getOrCreateInstance(t.delegateTarget,this._getDelegateConfig())}_getOffset(){const{offset:t}=this._config;return"string"==typeof t?t.split(",").map((t=>Number.parseInt(t,10))):"function"==typeof t?e=>t(e,this._element):t}_resolvePossibleFunction(t){return"function"==typeof t?t.call(this._element):t}_getPopperConfig(t){const e={placement:t,modifiers:[{name:"flip",options:{fallbackPlacements:this._config.fallbackPlacements}},{name:"offset",options:{offset:this._getOffset()}},{name:"preventOverflow",options:{boundary:this._config.boundary}},{name:"arrow",options:{element:`.${this.constructor.NAME}-arrow`}},{name:"onChange",enabled:!0,phase:"afterWrite",fn:t=>this._handlePopperPlacementChange(t)}],onFirstUpdate:t=>{t.options.placement!==t.placement&&this._handlePopperPlacementChange(t)}};return{...e,..."function"==typeof this._config.popperConfig?this._config.popperConfig(e):this._config.popperConfig}}_addAttachmentClass(t){this.getTipElement().classList.add(`${this._getBasicClassPrefix()}-${this.updateAttachment(t)}`)}_getAttachment(t){return Ji[t.toUpperCase()]}_setListeners(){this._config.trigger.split(" ").forEach((t=>{if("click"===t)j.on(this._element,this.constructor.Event.CLICK,this._config.selector,(t=>this.toggle(t)));else if("manual"!==t){const e=t===hn?this.constructor.Event.MOUSEENTER:this.constructor.Event.FOCUSIN,i=t===hn?this.constructor.Event.MOUSELEAVE:this.constructor.Event.FOCUSOUT;j.on(this._element,e,this._config.selector,(t=>this._enter(t))),j.on(this._element,i,this._config.selector,(t=>this._leave(t)))}})),this._hideModalHandler=()=>{this._element&&this.hide()},j.on(this._element.closest(ln),cn,this._hideModalHandler),this._config.selector?this._config={...this._config,trigger:"manual",selector:""}:this._fixTitle()}_fixTitle(){const t=this._element.getAttribute("title"),e=typeof this._element.getAttribute("data-bs-original-title");(t||"string"!==e)&&(this._element.setAttribute("data-bs-original-title",t||""),!t||this._element.getAttribute("aria-label")||this._element.textContent||this._element.setAttribute("aria-label",t),this._element.setAttribute("title",""))}_enter(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusin"===t.type?dn:hn]=!0),e.getTipElement().classList.contains(sn)||e._hoverState===on?e._hoverState=on:(clearTimeout(e._timeout),e._hoverState=on,e._config.delay&&e._config.delay.show?e._timeout=setTimeout((()=>{e._hoverState===on&&e.show()}),e._config.delay.show):e.show())}_leave(t,e){e=this._initializeOnDelegatedTarget(t,e),t&&(e._activeTrigger["focusout"===t.type?dn:hn]=e._element.contains(t.relatedTarget)),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=rn,e._config.delay&&e._config.delay.hide?e._timeout=setTimeout((()=>{e._hoverState===rn&&e.hide()}),e._config.delay.hide):e.hide())}_isWithActiveTrigger(){for(const t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1}_getConfig(t){const e=U.getDataAttributes(this._element);return Object.keys(e).forEach((t=>{Gi.has(t)&&delete e[t]})),(t={...this.constructor.Default,...e,..."object"==typeof t&&t?t:{}}).container=!1===t.container?document.body:r(t.container),"number"==typeof t.delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),a(Qi,t,this.constructor.DefaultType),t.sanitize&&(t.template=Yi(t.template,t.allowList,t.sanitizeFn)),t}_getDelegateConfig(){const t={};for(const e in this._config)this.constructor.Default[e]!==this._config[e]&&(t[e]=this._config[e]);return t}_cleanTipClass(){const t=this.getTipElement(),e=new RegExp(`(^|\\s)${this._getBasicClassPrefix()}\\S+`,"g"),i=t.getAttribute("class").match(e);null!==i&&i.length>0&&i.map((t=>t.trim())).forEach((e=>t.classList.remove(e)))}_getBasicClassPrefix(){return"bs-tooltip"}_handlePopperPlacementChange(t){const{state:e}=t;e&&(this.tip=e.elements.popper,this._cleanTipClass(),this._addAttachmentClass(this._getAttachment(e.placement)))}_disposePopper(){this._popper&&(this._popper.destroy(),this._popper=null)}static jQueryInterface(t){return this.each((function(){const e=un.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}g(un);const fn={...un.Default,placement:"right",offset:[0,8],trigger:"click",content:"",template:''},pn={...un.DefaultType,content:"(string|element|function)"},mn={HIDE:"hide.bs.popover",HIDDEN:"hidden.bs.popover",SHOW:"show.bs.popover",SHOWN:"shown.bs.popover",INSERTED:"inserted.bs.popover",CLICK:"click.bs.popover",FOCUSIN:"focusin.bs.popover",FOCUSOUT:"focusout.bs.popover",MOUSEENTER:"mouseenter.bs.popover",MOUSELEAVE:"mouseleave.bs.popover"};class gn extends un{static get Default(){return fn}static get NAME(){return"popover"}static get Event(){return mn}static get DefaultType(){return pn}isWithContent(){return this.getTitle()||this._getContent()}setContent(t){this._sanitizeAndSetContent(t,this.getTitle(),".popover-header"),this._sanitizeAndSetContent(t,this._getContent(),".popover-body")}_getContent(){return this._resolvePossibleFunction(this._config.content)}_getBasicClassPrefix(){return"bs-popover"}static jQueryInterface(t){return this.each((function(){const e=gn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}g(gn);const _n="scrollspy",bn={offset:10,method:"auto",target:""},vn={offset:"number",method:"string",target:"(string|element)"},yn="active",wn=".nav-link, .list-group-item, .dropdown-item",En="position";class An extends B{constructor(t,e){super(t),this._scrollElement="BODY"===this._element.tagName?window:this._element,this._config=this._getConfig(e),this._offsets=[],this._targets=[],this._activeTarget=null,this._scrollHeight=0,j.on(this._scrollElement,"scroll.bs.scrollspy",(()=>this._process())),this.refresh(),this._process()}static get Default(){return bn}static get NAME(){return _n}refresh(){const t=this._scrollElement===this._scrollElement.window?"offset":En,e="auto"===this._config.method?t:this._config.method,n=e===En?this._getScrollTop():0;this._offsets=[],this._targets=[],this._scrollHeight=this._getScrollHeight(),V.find(wn,this._config.target).map((t=>{const s=i(t),o=s?V.findOne(s):null;if(o){const t=o.getBoundingClientRect();if(t.width||t.height)return[U[e](o).top+n,s]}return null})).filter((t=>t)).sort(((t,e)=>t[0]-e[0])).forEach((t=>{this._offsets.push(t[0]),this._targets.push(t[1])}))}dispose(){j.off(this._scrollElement,".bs.scrollspy"),super.dispose()}_getConfig(t){return(t={...bn,...U.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}}).target=r(t.target)||document.documentElement,a(_n,t,vn),t}_getScrollTop(){return this._scrollElement===window?this._scrollElement.pageYOffset:this._scrollElement.scrollTop}_getScrollHeight(){return this._scrollElement.scrollHeight||Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)}_getOffsetHeight(){return this._scrollElement===window?window.innerHeight:this._scrollElement.getBoundingClientRect().height}_process(){const t=this._getScrollTop()+this._config.offset,e=this._getScrollHeight(),i=this._config.offset+e-this._getOffsetHeight();if(this._scrollHeight!==e&&this.refresh(),t>=i){const t=this._targets[this._targets.length-1];this._activeTarget!==t&&this._activate(t)}else{if(this._activeTarget&&t0)return this._activeTarget=null,void this._clear();for(let e=this._offsets.length;e--;)this._activeTarget!==this._targets[e]&&t>=this._offsets[e]&&(void 0===this._offsets[e+1]||t`${e}[data-bs-target="${t}"],${e}[href="${t}"]`)),i=V.findOne(e.join(","),this._config.target);i.classList.add(yn),i.classList.contains("dropdown-item")?V.findOne(".dropdown-toggle",i.closest(".dropdown")).classList.add(yn):V.parents(i,".nav, .list-group").forEach((t=>{V.prev(t,".nav-link, .list-group-item").forEach((t=>t.classList.add(yn))),V.prev(t,".nav-item").forEach((t=>{V.children(t,".nav-link").forEach((t=>t.classList.add(yn)))}))})),j.trigger(this._scrollElement,"activate.bs.scrollspy",{relatedTarget:t})}_clear(){V.find(wn,this._config.target).filter((t=>t.classList.contains(yn))).forEach((t=>t.classList.remove(yn)))}static jQueryInterface(t){return this.each((function(){const e=An.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}j.on(window,"load.bs.scrollspy.data-api",(()=>{V.find('[data-bs-spy="scroll"]').forEach((t=>new An(t)))})),g(An);const Tn="active",On="fade",Cn="show",kn=".active",Ln=":scope > li > .active";class xn extends B{static get NAME(){return"tab"}show(){if(this._element.parentNode&&this._element.parentNode.nodeType===Node.ELEMENT_NODE&&this._element.classList.contains(Tn))return;let t;const e=n(this._element),i=this._element.closest(".nav, .list-group");if(i){const e="UL"===i.nodeName||"OL"===i.nodeName?Ln:kn;t=V.find(e,i),t=t[t.length-1]}const s=t?j.trigger(t,"hide.bs.tab",{relatedTarget:this._element}):null;if(j.trigger(this._element,"show.bs.tab",{relatedTarget:t}).defaultPrevented||null!==s&&s.defaultPrevented)return;this._activate(this._element,i);const o=()=>{j.trigger(t,"hidden.bs.tab",{relatedTarget:this._element}),j.trigger(this._element,"shown.bs.tab",{relatedTarget:t})};e?this._activate(e,e.parentNode,o):o()}_activate(t,e,i){const n=(!e||"UL"!==e.nodeName&&"OL"!==e.nodeName?V.children(e,kn):V.find(Ln,e))[0],s=i&&n&&n.classList.contains(On),o=()=>this._transitionComplete(t,n,i);n&&s?(n.classList.remove(Cn),this._queueCallback(o,t,!0)):o()}_transitionComplete(t,e,i){if(e){e.classList.remove(Tn);const t=V.findOne(":scope > .dropdown-menu .active",e.parentNode);t&&t.classList.remove(Tn),"tab"===e.getAttribute("role")&&e.setAttribute("aria-selected",!1)}t.classList.add(Tn),"tab"===t.getAttribute("role")&&t.setAttribute("aria-selected",!0),u(t),t.classList.contains(On)&&t.classList.add(Cn);let n=t.parentNode;if(n&&"LI"===n.nodeName&&(n=n.parentNode),n&&n.classList.contains("dropdown-menu")){const e=t.closest(".dropdown");e&&V.find(".dropdown-toggle",e).forEach((t=>t.classList.add(Tn))),t.setAttribute("aria-expanded",!0)}i&&i()}static jQueryInterface(t){return this.each((function(){const e=xn.getOrCreateInstance(this);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t]()}}))}}j.on(document,"click.bs.tab.data-api",'[data-bs-toggle="tab"], [data-bs-toggle="pill"], [data-bs-toggle="list"]',(function(t){["A","AREA"].includes(this.tagName)&&t.preventDefault(),c(this)||xn.getOrCreateInstance(this).show()})),g(xn);const Dn="toast",Sn="hide",Nn="show",In="showing",Pn={animation:"boolean",autohide:"boolean",delay:"number"},jn={animation:!0,autohide:!0,delay:5e3};class Mn extends B{constructor(t,e){super(t),this._config=this._getConfig(e),this._timeout=null,this._hasMouseInteraction=!1,this._hasKeyboardInteraction=!1,this._setListeners()}static get DefaultType(){return Pn}static get Default(){return jn}static get NAME(){return Dn}show(){j.trigger(this._element,"show.bs.toast").defaultPrevented||(this._clearTimeout(),this._config.animation&&this._element.classList.add("fade"),this._element.classList.remove(Sn),u(this._element),this._element.classList.add(Nn),this._element.classList.add(In),this._queueCallback((()=>{this._element.classList.remove(In),j.trigger(this._element,"shown.bs.toast"),this._maybeScheduleHide()}),this._element,this._config.animation))}hide(){this._element.classList.contains(Nn)&&(j.trigger(this._element,"hide.bs.toast").defaultPrevented||(this._element.classList.add(In),this._queueCallback((()=>{this._element.classList.add(Sn),this._element.classList.remove(In),this._element.classList.remove(Nn),j.trigger(this._element,"hidden.bs.toast")}),this._element,this._config.animation)))}dispose(){this._clearTimeout(),this._element.classList.contains(Nn)&&this._element.classList.remove(Nn),super.dispose()}_getConfig(t){return t={...jn,...U.getDataAttributes(this._element),..."object"==typeof t&&t?t:{}},a(Dn,t,this.constructor.DefaultType),t}_maybeScheduleHide(){this._config.autohide&&(this._hasMouseInteraction||this._hasKeyboardInteraction||(this._timeout=setTimeout((()=>{this.hide()}),this._config.delay)))}_onInteraction(t,e){switch(t.type){case"mouseover":case"mouseout":this._hasMouseInteraction=e;break;case"focusin":case"focusout":this._hasKeyboardInteraction=e}if(e)return void this._clearTimeout();const i=t.relatedTarget;this._element===i||this._element.contains(i)||this._maybeScheduleHide()}_setListeners(){j.on(this._element,"mouseover.bs.toast",(t=>this._onInteraction(t,!0))),j.on(this._element,"mouseout.bs.toast",(t=>this._onInteraction(t,!1))),j.on(this._element,"focusin.bs.toast",(t=>this._onInteraction(t,!0))),j.on(this._element,"focusout.bs.toast",(t=>this._onInteraction(t,!1)))}_clearTimeout(){clearTimeout(this._timeout),this._timeout=null}static jQueryInterface(t){return this.each((function(){const e=Mn.getOrCreateInstance(this,t);if("string"==typeof t){if(void 0===e[t])throw new TypeError(`No method named "${t}"`);e[t](this)}}))}}return R(Mn),g(Mn),{Alert:W,Button:z,Carousel:st,Collapse:pt,Dropdown:hi,Modal:Hi,Offcanvas:Fi,Popover:gn,ScrollSpy:An,Tab:xn,Toast:Mn,Tooltip:un}})); +//# sourceMappingURL=bootstrap.bundle.min.js.map \ No newline at end of file diff --git a/public/css/style.css b/public/css/style.css new file mode 100644 index 0000000..e69de29 diff --git a/public/dmxAppConnect/config.js b/public/dmxAppConnect/config.js new file mode 100644 index 0000000..b18721d --- /dev/null +++ b/public/dmxAppConnect/config.js @@ -0,0 +1,8 @@ +dmx.config({ + "index": { + "repeat1": { + "meta": null, + "outputType": "text" + } + } +}); diff --git a/public/dmxAppConnect/dmxAppConnect.js b/public/dmxAppConnect/dmxAppConnect.js new file mode 100644 index 0000000..b225cbf --- /dev/null +++ b/public/dmxAppConnect/dmxAppConnect.js @@ -0,0 +1,8 @@ +/*! + App Connect + Version: 1.14.12 + (c) 2023 Wappler.io + @build 2023-03-14 12:22:18 + */ +window.Element&&!Element.prototype.closest&&(Element.prototype.closest=function(t){var e,n=(this.document||this.ownerDocument).querySelectorAll(t),i=this;do{for(e=n.length;--e>=0&&n.item(e)!==i;);}while(e<0&&(i=i.parentElement));return i}),window.NodeList&&!NodeList.prototype.forEach&&(NodeList.prototype.forEach=Array.prototype.forEach),function(){window.dmx=window.dmx||{};var t=Object.prototype.toString,e=Object.prototype.hasOwnProperty,n=/\w*$/,i=/^(?:Float(?:32|64)|(?:Int|Uint)(?:8|16|32)|Uint8Clamped)Array$/,r=function(t,e){return t===e||t!=t&&e!=e},s=function(t,e){for(var n=t.length;n--;)if(r(t[n][0],e))return n;return-1},a=function(t,e){return function(t){var e=typeof t;return"string"==e||"number"==e||"symbol"==e||"boolean"==e?"__proto__"!==t:null===t}(e)?t["string"==typeof e?"string":"hash"]:t.map},o=function(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e-1},set:function(t,e){var n=this.__data__,i=s(n,t);return i<0?(++this.size,n.push([t,e])):n[i][1]=e,this}};var u=function(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e{t.state&&t.state.title&&(document.title=t.state.title),dmx.requestUpdate()}),0)},window.onhashchange=function(){dmx.requestUpdate()};var n=dmx.Component("app");dmx.app=new n(t,dmx.global),dmx.app.$update(),e&&e()}else e&&e()},document.documentElement.style.visibility="hidden",document.addEventListener("DOMContentLoaded",(()=>{Promise.all(dmx.__startup).then((()=>{var t=document.querySelector(':root[dmx-app], [dmx-app], :root[is="dmx-app"], [is="dmx-app"]');dmx.appConnect(t,(function(){document.documentElement.style.visibility="",t&&t.removeAttribute("dmx-app")}))}))})),dmx.useHistory=window.history&&window.history.pushState,dmx.extend=function(){var t={},e=!1,n=0,i=arguments.length;"[object Boolean]"===Object.prototype.toString.call(arguments[0])&&(e=arguments[0],n++);for(var r=function(n){for(var i in n)"__proto__"!=i&&Object.prototype.hasOwnProperty.call(n,i)&&(e&&"[object Object]"===Object.prototype.toString.call(n[i])?t[i]=dmx.extend(!0,t[i],n[i]):null!=n[i]&&(t[i]=n[i]))};nt.dirty=!0)),t.checkValidity()},dmx.validateReset=function(t){},(()=>{const t=[];window.addEventListener("message",(e=>{if(e.source===window&&"dmxNextTick"===e.data&&t.length){const e=t.shift();e.fn.call(e.context)}})),dmx.nextTick=(e,n)=>{t.push({fn:e,context:n}),window.postMessage("dmxNextTick","*")}})(),dmx.requestUpdate=function(){var t=!1,e=new Set;return function(n){e.add(n||"*"),t||(t=!0,dmx.nextTick((function(){if(t=!1,dmx.app){var n=new Set(e);e.clear(),dmx.app.$update(n)}})))}}(),dmx.debounce=function(t,e){let n;return function(){const i=()=>{t.apply(this,arguments)};e?(clearTimeout(n),n=setTimeout(i,e)):(cancelAnimationFrame(n),n=requestAnimationFrame(i))}},dmx.throttle=function(t,e){let n,i=!1;return function(){if(n=Array.from(arguments),!i){const r=()=>{i=!1,n&&t.apply(this,n)};t.apply(this,n),n=void 0,i=!0,e?setTimeout(r,e):requestAnimationFrame(r)}}},dmx.keyCodes={bs:8,tab:9,enter:13,esc:27,space:32,left:37,up:38,right:39,down:40,delete:46,backspace:8,pause:19,capslock:20,escape:27,pageup:33,pagedown:34,end:35,home:36,arrowleft:37,arrowup:38,arrowright:39,arrowdown:40,insert:45,numlock:144,scrolllock:145,semicolon:186,equal:187,comma:188,minus:189,period:190,slash:191,backquote:192,bracketleft:219,backslash:220,bracketright:221,quote:222,numpad0:96,numpad1:97,numpad2:98,numpad3:99,numpad4:100,numpad5:101,numpad6:102,numpad7:103,numpad8:104,numpad9:105,numpadmultiply:106,numpadadd:107,numpadsubstract:109,numpaddivide:111,f1:112,f2:113,f3:114,f4:115,f5:116,f6:117,f7:118,f8:119,f9:120,f10:121,f11:122,f12:123,digit0:48,digit1:49,digit2:50,digit3:51,digit4:52,digit5:53,digit6:54,digit7:55,digit8:56,digit9:57,keya:[65,97],keyb:[66,98],keyc:[67,99],keyd:[68,100],keye:[69,101],keyf:[70,102],keyg:[71,103],keyh:[72,104],keyi:[73,105],keyj:[74,106],keyk:[75,107],keyl:[76,108],keym:[77,109],keyn:[78,110],keyo:[79,111],keyp:[80,112],keyq:[81,113],keyr:[82,114],keys:[83,115],keyt:[84,116],keyu:[85,117],keyv:[86,118],keyw:[87,119],keyx:[88,120],keyy:[89,121],keyz:[90,122]},dmx.eventListener=function(t,e,n,i){let r,s;const a=function(t){if((!i.self||t.target===t.currentTarget)&&(!i.ctrl||t.ctrlKey)&&(!i.alt||t.altKey)&&(!i.shift||t.shiftKey)&&(!i.meta||t.metaKey)&&(!(t.originalEvent||t).nsp||Object.keys(i).includes((t.originalEvent||t).nsp))){if((t.originalEvent||t)instanceof MouseEvent){if(null!=i.button&&t.button!=(parseInt(i.button,10)||0))return;if(i.button0&&0!=t.button)return;if(i.button1&&1!=t.button)return;if(i.button2&&2!=t.button)return;if(i.button3&&3!=t.button)return;if(i.button4&&4!=t.button)return}if((t.originalEvent||t)instanceof KeyboardEvent){var e=[];Object.keys(i).forEach((function(t){var n=parseInt(t,10);n?e.push(n):dmx.keyCodes[t]&&e.push(dmx.keyCodes[t])}));for(var a=0;a({identifier:t.identifier,screenX:t.screenX,screenY:t.screenY,clientX:t.clientX,clientY:t.clientY,pageX:t.pageX,pageY:t.pageY});t.$data.altKey=t.altKey,t.$data.ctrlKey=t.ctrlKey,t.$data.metaKey=t.metaKey,t.$data.shiftKey=t.shiftKey,t.$data.touches=Array.from(t.touches).map(e),t.$data.changedTouches=Array.from(t.changedTouches).map(e),t.$data.targetTouches=Array.from(t.targetTouches).map(e),t.$data.rotation=t.rotation,t.$data.scale=t.scale}if(t instanceof KeyboardEvent&&(t.$data.altKey=t.altKey,t.$data.ctrlKey=t.ctrlKey,t.$data.metaKey=t.metaKey,t.$data.shiftKey=t.shiftKey,t.$data.location=t.location,t.$data.repeat=t.repeat,t.$data.code=t.code,t.$data.key=t.key),i.debounce)clearTimeout(r),r=setTimeout((()=>{n.apply(this,arguments)}),parseInt(i.debounce,10)||0);else{if(!i.throttle)return n.apply(this,arguments);s||(s=!0,n.apply(this,arguments),setTimeout((()=>{s=!1}),parseInt(i.throttle,10)||0))}}};i=i||{},window.Dom7&&1===t.nodeType?Dom7(t)[i.once?"once":"on"](e.replace(/-/g,"."),a,!!i.capture):window.jQuery&&!i.capture?jQuery(t)[i.once?"one":"on"](e.replace(/-/g,"."),a):t.addEventListener(e.replace(/-/g,"."),a,{capture:!!i.capture,once:!!i.once,passive:!!i.passive})},dmx.createClass=function(t,e){var n=function(){t.constructor&&t.constructor.apply(this,arguments)};return e&&e.prototype&&(n.prototype=Object.create(e.prototype)),Object.assign(n.prototype,t),n.prototype.constructor=n,n},dmx.Config=function(t){Object.assign(dmx.config,t)},dmx.Component=function(t,e){if(e){var n=dmx.Component(e.extends)||dmx.BaseComponent;e.initialData=Object.assign({},n.prototype.initialData,e.initialData),e.attributes=Object.assign({},n.prototype.attributes,e.attributes),e.methods=Object.assign({},n.prototype.methods,e.methods),e.events=Object.assign({},n.prototype.events,e.events),e.hasOwnProperty("constructor")||(e.constructor=function(t,e){n.call(this,t,e)}),e.type=t;var i=dmx.createClass(e,n);i.extends=e.extends,dmx.__components[t]=i}return dmx.__components[t]},dmx.Attribute=function(t,e,n){dmx.__attributes[e]||(dmx.__attributes[e]={}),dmx.__attributes[e][t]=n},dmx.Formatters=function(t,e){for(var n in dmx.__formatters[t]||(dmx.__formatters[t]={}),e)e.hasOwnProperty(n)&&(dmx.__formatters[t][n]=e[n])},dmx.Formatter=function(t,e,n){dmx.__formatters[t]||(dmx.__formatters[t]={}),dmx.__formatters[t][e]=n},dmx.Adapter=function(t,e,n){return dmx.__adapters[t]||(dmx.__adapters[t]={}),n&&(dmx.__adapters[t][e]=n),dmx.__adapters[t][e]},dmx.Actions=function(t){for(var e in t)t.hasOwnProperty(e)&&(dmx.__actions[e]=t[e])},dmx.Action=function(t,e){dmx.__actions[t]=e},dmx.Startup=function(t){dmx.__startup.add(t)},"app:"==document.location.protocol&&dmx.Startup(new Promise((t=>document.addEventListener("deviceready",t)))),function(){var t=function(e){if(!(this instanceof t))return new t(e);if(e instanceof t)return e;if(!e)return this;var n=e.length;if(e.nodeType)this[0]=e,this.length=1;else{if("string"==typeof e)return t(document.querySelectorAll(e));if(n)for(var i=0;i0?a[t.substr(0,n)]=t.substr(n+1):a[t]=!0}}));var o=r.indexOf(":");o>0&&(s=r.substr(o+1),r=r.substr(0,o)),e.push({name:r,fullName:i.name,value:i.value,argument:s,modifiers:a})}}return e},remove:function(t){Array.isArray(t)?t.forEach((function(t){dmx.dom.remove(t)})):t.remove()},replace:function(t,e){t.parentNode&&t.parentNode.replaceChild(e,t)}}}(),function(){var t={},e={Boolean:"boolean",Null:"null",Undefinec:"undefined",Number:"number",BigInt:"number",String:"string",Date:"date",RegExp:"regexp",Blob:"blob",File:"file",FileList:"filelist",ArrayBuffer:"arraybuffer",ImageBitmap:"imagebitmap",ImageData:"imagedata",Array:"array",Object:"object",Map:"map",Set:"set",DataView:"array",Int8Array:"array",Uint8Array:"array",Uint8ClampedArray:"array",Int16Array:"array",Uint16Array:"array",Int32Array:"array",Uint32Array:"array",Float32Array:"array",Float64Array:"array",BigInt64Array:"array",BigUint64Array:"array"},n={"{":"L_CURLY","}":"R_CURLY","(":"L_PAREN",")":"R_PAREN","[":"L_BRACKET","]":"R_BRACKET",".":"PERIOD",",":"COMMA",";":"SEMI",":":"COLON","?":"QUESTION","+":"ADDICTIVE","-":"ADDICTIVE","*":"MULTIPLICATIVE","/":"MULTIPLICATIVE","%":"MULTIPLICATIVE","===":"EQUALITY","!==":"EQUALITY","==":"EQUALITY","!=":"EQUALITY","<":"RELATIONAL",">":"RELATIONAL","<=":"RELATIONAL",">=":"RELATIONAL",in:"RELATIONAL","&&":"LOGICAL_AND","||":"LOGICAL_OR","!":"LOGICAL_NOT","&":"BITWISE_AND","|":"BITWISE_OR","^":"BITWISE_XOR","~":"BITWISE_NOT","<<":"BITWISE_SHIFT",">>":"BITWISE_SHIFT",">>>":"BITWISE_SHIFT"},i={n:"\n",f:"\f",r:"\r",t:"\t",v:"\v","'":"'",'"':'"',"`":"`"},r={"**":function(t,e){return Math.pow(t(),e())},"??":function(t,e){return null==(t=t())?e():t},in:function(t,e){return t()in e()},"?":function(t,e,n){return t()?e():n()},"+":function(t,e){return t=t(),e=e(),null==t?e:null==e?t:t+e},"-":function(t,e){return t()-e()},"*":function(t,e){return t()*e()},"/":function(t,e){return t()/e()},"%":function(t,e){return t()%e()},"===":function(t,e){return t()===e()},"!==":function(t,e){return t()!==e()},"==":function(t,e){return t()==e()},"!=":function(t,e){return t()!=e()},"<":function(t,e){return t()":function(t,e){return t()>e()},"<=":function(t,e){return t()<=e()},">=":function(t,e){return t()>=e()},"&&":function(t,e){return t()&&e()},"||":function(t,e){return t()||e()},"&":function(t,e){return t()&e()},"|":function(t,e){return t()|e()},"^":function(t,e){return t()^e()},"<<":function(t,e){return t()<>":function(t,e){return t()>>e()},">>>":function(t,e){return t()>>>e()},"~":function(t){return~t()},"!":function(t){return!t()}},s={this:function(t){return function(){return t.data}},undefined:function(){return function(){}},null:function(){return function(){return null}},true:function(){return function(){return!0}},false:function(){return function(){return!1}},_:function(){return function(){return{__dmxScope__:!0}}}},a=function(){return 0};dmx.getType=function(t){return e[Object.prototype.toString.call(t).slice(8,-1)]},dmx.getIdents=function(t){if("string"!=typeof t)return[];let e=[];if(t.includes("{{")){let n;for(;null!=(n=dmx.reExpressionReplace.exec(t));)e=e.concat(dmx.lexer(n[1]))}else e=dmx.lexer(t);return e.reduce(((t,e)=>("IDENT"==e.name?t.push(e.value):"STRING"==e.name&&e.value.startsWith("{{")&&(t=t.concat(dmx.getIdents(e.value))),t)),[])},dmx.lexer=function(e){if(t[e])return t[e];for(var r,s,a,o,d,u,c=[],h=0,l=!0;h1?e.substr(h,t):e[h]}function f(t){return h+(t=t||1)="0"&&t<="9"}function y(t){return t>="a"&&t<="z"||t>="A"&&t<="Z"||"_"==t||"$"==t}function g(t){return y(t)||x(t)}function b(t){return" "==t||"\r"==t||"\t"==t||"\n"==t||"\v"==t||" "==t}function E(t){return"-"==t||"+"==t||x(t)}function w(t){var n="",r=!1;for(h++;h0&&!(u("R_PAREN")||u("R_BRACKET")||u("R_CURLY")||u("COMMA")||u("SEMI"))&&t.push(p()),!c("COMMA")&&!c("SEMI"))return(1==t.length?t[0]:e)();function e(){for(var e,n=0;n0){var e=o[0];if(!t||e.name==t)return e}return!1}function c(t){var e=u(t);return!!e&&(o.shift(),e)}function h(e){if(!c(e))throw Error("Unexpected token, expecting ["+e+"] in expression ["+t+"]")}function l(t){var n=Array.prototype.slice.call(arguments,1);return function(){return r.hasOwnProperty(t)?r[t].apply(e,n):t}}function p(){return f()}function f(){var e,n=function(){var t,e=m();for(;;){if(!(t=c("LOGICAL_OR")))return e;e=l(t.value,e,m())}}();if(c("QUESTION")){if(e=f(),c("COLON"))return l("?",n,e,f());throw Error('Expecting a ":" in expression ['+t+"]")}return n}function m(){var t,e=function(){var t,e=v();(t=c("BITWISE_OR"))&&(e=l(t.value,e,v()));return e}();return(t=c("LOGICAL_AND"))&&(e=l(t.value,e,m())),e}function v(){var t,e=x();return(t=c("BITWISE_XOR"))&&(e=l(t.value,e,x())),e}function x(){var t,e=y();return(t=c("BITWISE_AND"))&&(e=l(t.value,e,x())),e}function y(){var t,e=g();return(t=c("EQUALITY"))&&(e=l(t.value,e,y())),e}function g(){var t,e=function(){var t,e=b();for(;t=c("BITWISE_SHIFT");)e=l(t.value,e,b());return e}();return(t=c("RELATIONAL"))&&(e=l(t.value,e,g())),e}function b(){for(var t,e=E();t=c("ADDICTIVE");)e=l(t.value,e,E());return e}function E(){for(var t,e=w();t=c("MULTIPLICATIVE");)e=l(t.value,e,w());return e}function w(){var t;return(t=c("ADDICTIVE"))?"+"==t.value?_():l(t.value,a,w()):(t=c("LOGICAL_NOT"))?l(t.value,w()):_()}function _(){var n,r;if(c("L_PAREN"))n=p(),h("R_PAREN");else if(c("L_CURLY")){var a={};if("R_CURLY"!=d().name)do{var o=c().value;h("COLON"),a[o]=p()()}while(c("COMMA"));n=l(a),h("R_CURLY")}else if(c("L_BRACKET")){var f=[];if("R_BRACKET"!=d().name)do{f.push(p()())}while(c("COMMA"));n=l(f),h("R_BRACKET")}else if(c("PERIOD"))n=u()?A(l(e.data)):l(e.data);else{var m=c();if(!1===m)throw Error("Not a primary expression ["+t+"]");n="IDENT"==m.name?s.hasOwnProperty(m.value)?s[m.value](e):function(){return e.get(m.value)}:"METHOD"==m.name?l(dmx.__formatters.global[m.value]||function(){window.warn&&console.warn("Formatter "+m.value+" in expression ["+t+"] doesn't exist")}):function(){return m.value}}for(;r=c("L_PAREN")||c("L_BRACKET")||c("PERIOD");)if("("==r.value)n=$(n,i);else if("["==r.value)i=n,n=k(n);else{if("."!=r.value)throw Error("Parse Error in expression ["+t+"]");i=n,n=A(n)}return i=null,n}function $(t,n){var i=[];if("R_PAREN"!=d().name)do{i.push(p())}while(c("COMMA"));return h("R_PAREN"),function(){var r=[];n&&r.push(n());for(var s=0;s-1&&r){var s=new r(n,e.parent);e.parent.children.splice(i,1,s),s.name&&e.parent.add(s.name,s.data)}}dmx.requestUpdate()}},__remove:function(t){var e=this.__find(t);if(e){e.$destroy();var n=e.parent.children.indexOf(this);n>-1&&e.parent.children.splice(n,1),dmx.requestUpdate()}},beforeMount:dmx.noop,mounted:dmx.noop,beforeUpdate:dmx.noop,update:dmx.noop,updated:dmx.noop,beforeDestroy:dmx.noop,destroyed:dmx.noop,addEventListener:function(t,e){t in this.listeners||(this.listeners[t]=new Set),this.listeners[t].add(e)},removeEventListener:function(t,e){t in this.listeners&&this.listeners[t].delete(e)},dispatchEvent:function(t,e,n,i){if("string"==typeof t)try{var r=this.events[t];t=new r(t,e)}catch(n){var s=t;if((t=document.createEvent("CustomEvent")).initEvent(s,e&&e.bubbles,e&&e.cancelable),!(t instanceof Event))return console.warn("Unknown event "+t,this.events),!1;var a=t.preventDefault;t.preventDefault=function(){a.call(this);try{Object.defineProperty(this,"defaultPrevented",{get:function(){return!0}})}catch(t){this.defaultPrevented=!0}return t}}if(!(t.type in this.listeners))return!0;t.nsp=i,t.target=this,t.$data=n||{};for(let e of this.listeners[t.type])!1===e.call(this,t)&&t.preventDefault();return!t.defaultPrevented},$addChild:function(t,e){var n=new(0,dmx.__components[t])(e,this);this.children.push(n),n.name&&(this.data[n.name]&&dmx.debug&&console.warn('Duplicate name "'+n.name+'" found, component not added to scope.'),this.set(n.name,n.data))},$customAttributes:function(t,e){dmx.dom.getAttributes(e).forEach((function(n){dmx.__attributes[t][n.name]&&(e.removeAttribute(n.fullName),dmx.__attributes[t][n.name].call(this,e,n))}),this)},$parse:function(t){if(t=t||this.$node)if(3!==t.nodeType)1===t.nodeType&&(dmx.config.mapping&&Object.keys(dmx.config.mapping).forEach((function(e){dmx.array(t.querySelectorAll(e)).forEach((function(t){t.hasAttribute("is")||t.setAttribute("is","dmx-"+dmx.config.mapping[e])}))})),dmx.dom.walk(t,(function(t){if(t!=this.$node){if(1===t.nodeType){var e=t.tagName.toLowerCase(),n=dmx.dom.getAttributes(t);if(t.hasAttribute("is")&&(e=t.getAttribute("is")),dmx.reIgnoreElement.test(e))return!1;if(this.$customAttributes("before",t),-1!==n.findIndex((function(t){return"repeat"===t.name})))return!1;if(dmx.rePrefixed.test(e))return(e=e.replace(/^dmx-/i,""))in dmx.__components?(t.isComponent=!0,t.dmxRendered?window.__WAPPLER__&&t.dmxComponent&&t.dmxComponent.$parse&&(dmx.reIgnoreElement.test(t.tagName)||t.dmxComponent.$parse()):this.$addChild(e,t),!1):void console.warn("Unknown component found! "+e);this.$customAttributes("mounted",t)}if(3===t.nodeType&&dmx.reExpression.test(t.nodeValue)){var i=t.nodeValue;"{{"==i.substr(0,2)&&"}}"==i.substr(-2)&&-1==i.indexOf("{{",2)&&(i=i.substring(2,i.length-2)),this.$addBinding(i,(function(e,n){t.nodeValue=e}))}}}),this));else if(dmx.reExpression.test(t.nodeValue)){var e=t.nodeValue;"{{"==e.substr(0,2)&&"}}"==e.substr(-2)&&-1==e.indexOf("{{",2)&&(e=e.substring(2,e.length-2)),this.$addBinding(e,(function(e,n){t.nodeValue=e}))}},$update:function(t){try{if(!1!==this.beforeUpdate(t)){const e=dmx.clone(this.props),n=(this.$updateBindings(this.bindings,t),this.$updatePropBindings(t));try{this.update(e,n)}catch(t){console.error(t)}this.children.forEach((function(e){e.$update(t)})),this.updated()}}catch(t){console.error(t)}},$canSkip:function(t,e){if(e&&!e.has("*")){for(const n of t.idents)if(e.has(n))return!1;return!0}return!1},$updateBindings:function(t,e){let n=!1;for(const i in t)if(t.hasOwnProperty(i)){const r=t[i];if(this.$canSkip(r,e))continue;const s=dmx.parse(i,this);if(!dmx.equal(s,r.value)){for(const t of r.callbacks)t.call(this,s,r.value);n=!0,r.value=dmx.clone(s)}}return n},$updatePropBindings:function(t){const e=new Set;for(const n in this.propBindings)if(this.propBindings.hasOwnProperty(n)){const i=this.propBindings[n];if(this.$canSkip(i,t))continue;const r=dmx.parse(i.expression,this);dmx.equal(r,i.value)||(i.callback.call(this,r),i.value=dmx.clone(r),e.add(n))}return e},$parseAttributes:function(t){var e=this;this.attributes&&(Object.keys(this.attributes).forEach((function(n){var i=e.attributes[n],r=i.default;if(t.hasAttribute(n)&&(i.type==Boolean?r=!0:(r=t.getAttribute(n),i.type==Number&&r&&!isNaN(Number(r))&&(r=Number(r)),i.type==String&&(r=String(r)),i.validate&&!i.validate(r)&&(r=i.default)),t.removeAttribute(n)),t.hasAttribute("dmx-bind:"+n)){const e=t.getAttribute("dmx-bind:"+n),i=this.$propBinding(n).bind(this);this.propBindings[n]={expression:e,callback:i,idents:dmx.getIdents(e),value:null},t.removeAttribute("dmx-bind:"+n)}e.props[n]=dmx.clone(r)}),this),this.$updatePropBindings()),this.events&&Object.keys(this.events).forEach((function(n){t.hasAttribute("on"+n)&&(dmx.eventListener(e,n,Function("event",t.getAttribute("on"+n)),{}),t.removeAttribute("on"+n))})),dmx.dom.getAttributes(t).forEach((function(n){"on"==n.name&&this.events[n.argument]&&(dmx.eventListener(e,n.argument,(function(t){return t.originalEvent&&(t=t.originalEvent),dmx.parse(n.value,dmx.DataScope({$event:t.$data,$originalEvent:t},e))}),n.modifiers),t.removeAttribute(n.fullName))}),this)},$propBinding:function(t){var e=this.attributes[t],n=this;return function(i){void 0===i&&(i=e.default),e.type==Boolean&&(i=!!i),null!=i&&(e.type==Number&&(i=""===i||isNaN(Number(i))?e.default:Number(i)),e.type==String&&(i=String(i))),e.validate&&!e.validate(i)&&(i=e.default),n.props[t]=dmx.clone(i)}},$initialData:function(){Object.assign(this.data,{$type:this.type},"function"==typeof this.initialData?this.initialData():this.initialData),Object.keys(this.methods).forEach((function(t){var e=this;this.data["__"+t]=function(){return e.methods[t].apply(e,Array.prototype.slice.call(arguments,1))}}),this)},$mount:function(t){this.$placeholder&&this.$node&&dmx.dom.replace(this.$placeholder,this.$node)},$addBinding:function(t,e){this.bindings[t]=this.bindings[t]||{expression:t,value:dmx.parse(t,this),callbacks:[],idents:dmx.getIdents(t)},this.bindings[t].callbacks.push(e),e.call(this,this.bindings[t].value)},$destroy:function(){this.dispatchEvent("destroy"),this.beforeDestroy(),this.$destroyChildren(),this.parent&&this.parent.del(this.name),this.$node&&dmx.dom.remove(this.$node),this.destroyed()},$destroyChildren:function(){this.children.forEach((function(t){t.$destroy()})),this.children=[]},updateRelated:function(){if(this.parent){let t=this.parent;for(;t;)!t.data||"repeat"!=t.data.$type&&"form-repeat"!=t.data.$type||dmx.requestUpdate(t.name),t=t.parent}},get:function(t,e){return this.data.hasOwnProperty(t)?this.data[t]:this.parent&&!0!==e?"parent"==t?this.parent.data:this.parent.get(t):null},add:function(t,e){this.data[t]?Array.isArray(this.data[t])?this.data[t].push(e):this.data[t]=[this.data[t],e]:this.set(t,e),dmx.requestUpdate(t),dmx.requestUpdate(this.name),this.updateRelated()},set:function(t,e){if("object"!=typeof t)dmx.equal(this.data[t],e)||(this.data[t]=e,dmx.requestUpdate(t),dmx.requestUpdate(this.name),this.updateRelated());else for(var n in t)t.hasOwnProperty(n)&&this.set(n,t[n])},del:function(t){delete this.data[t],dmx.requestUpdate(t),dmx.requestUpdate(this.name),this.updateRelated()}}),function(){dmx.pathToRegexp=o,dmx.pathToRegexp.parse=e,dmx.pathToRegexp.compile=function(t,i){return n(e(t,i))},dmx.pathToRegexp.tokensToFunction=n,dmx.pathToRegexp.tokensToRegExp=a;var t=new RegExp(["(\\\\.)","(?:\\:(\\w+)(?:\\(((?:\\\\.|[^\\\\()])+)\\))?|\\(((?:\\\\.|[^\\\\()])+)\\))([+*?])?"].join("|"),"g");function e(e,n){for(var s,a=[],o=0,d=0,u="",c=n&&n.delimiter||"/",h=n&&n.whitelist||void 0,l=!1;null!==(s=t.exec(e));){var p=s[0],f=s[1],m=s.index;if(u+=e.slice(d,m),d=m+p.length,f)u+=f[1],l=!0;else{var v="",x=s[2],y=s[3],g=s[4],b=s[5];if(!l&&u.length){var E=u.length-1,w=u[E];(!h||h.indexOf(w)>-1)&&(v=w,u=u.slice(0,E))}u&&(a.push(u),u="",l=!1);var _="+"===b||"*"===b,$="?"===b||"*"===b,k=y||g,A=v||c;a.push({name:x||o++,prefix:v,delimiter:A,optional:$,repeat:_,pattern:k?r(k):"[^"+i(A===c?A:A+c)+"]+?"})}}return(u||d0&&"\n"!==e[i];i--,r++);for(;i>0;i--)"\n"===e[i]&&s++;throw new Error(t+" at line "+s+","+r+" >>>"+e.substr(n-r,20)+" ...")}function o(){return i=e.charAt(n),n++,i}function d(t){return e.charAt(n+t)}function u(t){for(var e="",n=i;o();){if(i===n)return o(),t&&"'"===n&&"'"===i&&0===e.length?(o(),c()):e;if("\\"===i)if(o(),"u"===i){for(var s=0,d=0;d<4;d++){o();var u,h=i.charCodeAt(0);i>="0"&&i<="9"?u=h-48:i>="a"&&i<="f"?u=h-97+10:i>="A"&&i<="F"?u=h-65+10:a("Bad \\u char "+i),s=16*s+u}e+=String.fromCharCode(s)}else{if("string"!=typeof r[i])break;e+=r[i]}else"\n"===i||"\r"===i?a("Bad string containing newline"):e+=i}a("Bad string")}function c(){for(var t="",e=0,n=0;;){var r=d(-n-5);if(!r||"\n"===r)break;n++}function s(){for(var t=n;i&&i<=" "&&"\n"!==i&&t-- >0;)o()}for(;i&&i<=" "&&"\n"!==i;)o();for("\n"===i&&(o(),s());;){if(i){if("'"===i){if(e++,o(),3===e)return"\n"===t.slice(-1)&&(t=t.slice(0,-1)),t;continue}for(;e>0;)t+="'",e--}else a("Bad multiline string");"\n"===i?(t+="\n",o(),s()):("\r"!==i&&(t+=i),o())}}function h(){if('"'===i||"'"===i)return u(!1);for(var t="",e=n,r=-1;;){if(":"===i)return t?r>=0&&r!==t.length&&(n=e+r,a("Found whitespace in your key name (use quotes to include)")):a("Found ':' but no key name (for an empty key name use quotes)"),t;i<=" "?i?r<0&&(r=t.length):a("Found EOF while looking for a key name (check your syntax)"):s(i)?a("Found '"+i+"' where a key name was expected (check your syntax or use quotes if the key name includes {}[],: or whitespace)"):t+=i,o()}}function l(){for(;i;){for(;i&&i<=" ";)o();if("#"===i||"/"===i&&"/"===d(0))for(;i&&"\n"!==i;)o();else{if("/"!==i||"*"!==d(0))break;for(o(),o();i&&("*"!==i||"/"!==d(0));)o();i&&(o(),o())}}}function p(t,e){var n,i,r="",s=0,a=!0,o=0;function d(){return i=t.charAt(o),o++,i}for(d(),"-"===i&&(r="-",d());i>="0"&&i<="9";)a&&("0"==i?s++:a=!1),r+=i,d();if(a&&s--,"."===i)for(r+=".";d()&&i>="0"&&i<="9";)r+=i;if("e"===i||"E"===i)for(r+=i,d(),"-"!==i&&"+"!==i||(r+=i,d());i>="0"&&i<="9";)r+=i,d();for(;i&&i<=" ";)d();return e&&(","!==i&&"}"!==i&&"]"!==i&&"#"!==i&&("/"!==i||"/"!==t[o]&&"*"!==t[o])||(i=0)),n=+r,i||s||!isFinite(n)?void 0:n}function f(t){function e(t,n){var i,r,s,a;switch(typeof t){case"string":t.indexOf(n)>=0&&(a=t);break;case"object":if("[object Array]"===Object.prototype.toString.apply(t))for(i=0,s=t.length;i "+i+"\n (unquoted strings contain everything up to the next line!)":""}return n("}")||n("]")}function m(){var t=[];try{if(o(),l(),"]"===i)return o(),t;for(;i;){if(t.push(x()),l(),","===i&&(o(),l()),"]"===i)return o(),t;l()}a("End of input while parsing an array (missing ']')")}catch(e){throw e.hint=e.hint||f(t),e}}function v(t){var e="",n={};try{if(t||o(),l(),"}"===i&&!t)return o(),n;for(;i;){if(e=h(),l(),":"!==i&&a("Expected ':' instead of '"+i+"'"),o(),n[e]=x(),l(),","===i&&(o(),l()),"}"===i&&!t)return o(),n;l()}if(t)return n;a("End of input while parsing an object (missing '}')")}catch(t){throw t.hint=t.hint||f(n),t}}function x(){switch(l(),i){case"{":return v();case"[":return m();case"'":case'"':return u(!0);default:return function(){var t=i;for(s(i)&&a("Found a punctuator character '"+i+"' when expecting a quoteless string (check your syntax)");;){o();var e="\r"===i||"\n"===i||""===i;if(e||","===i||"}"===i||"]"===i||"#"===i||"/"===i&&("/"===d(0)||"*"===d(0))){var n=t[0];switch(n){case"f":if("false"===t.trim())return!1;break;case"n":if("null"===t.trim())return null;break;case"t":if("true"===t.trim())return!0;break;default:if("-"===n||n>="0"&&n<="9"){var r=p(t);if(void 0!==r)return r}}if(e)return t.trim()}t+=i}}()}}function y(t){return l(),i&&a("Syntax error, found trailing characters"),t}if("string"!=typeof t)throw new Error("source is not a string");return e=t,n=0,i=" ",function(){switch(l(),i){case"{":return y(v());case"[":return y(m());default:return y(x())}}()}),dmx.Flow=dmx.createClass({constructor:function(t){if(!(this instanceof dmx.Flow))return new dmx.Flow(t);window.Promise||console.warn("Promises are not supported, flows can not be used"),this._execStep=this._execStep.bind(this),this.scope=new dmx.DataScope({},t),this.output={}},run:function(t){var e=this;return this.output={},this._exec(t.exec||t).then((function(){return dmx.debug&&console.debug("finished",e.output),e.output}))},_each:function(t,e){return Promise.resolve(t).then((function(t){return(t=Array.isArray(t)?t:[t]).reduce((function(n,i,r){return n.then((function(){return e(i,r,t.length).then((function(e){e&&(t[r]=e)}))}))}),Promise.resolve()).then((function(){return t}))}))},_exec:function(t){var e=this;if(t.steps){var n=this._each(t.steps,this._execStep);return t.catch&&n.catch((function(n){return e._each(t.catch,e._execStep)})),n}return this._each(t,this._execStep)},_execStep:function(t){var e=this;for(var n in dmx.debug&&console.debug("exec step",t),t){if(t.hasOwnProperty(n)&&dmx.__actions[n]){var i=dmx.__actions[n].bind(this),r=t[n];return dmx.debug&&console.debug("exec action",n,r),r.disabled?Promise.resolve():Promise.resolve(i(r)).then((function(t){r.name&&(dmx.debug&&console.debug("set data",r.name,t),e.scope.set(r.name,t),r.output&&(dmx.debug&&console.debug("set output",r.name,t),e.output[r.name]=t))}))}throw new Error("Action "+n+" was not found.")}},parse:function(t){if(null==t)return t;if("object"==typeof(t=t.valueOf())){var e=t.slice?[]:{};for(var n in t)t.hasOwnProperty(n)&&(e[n]=this.parse(t[n],this.scope));return e}return"string"==typeof t&&-1!=t.indexOf("{{")?dmx.parse(t,this.scope):t}}),dmx.Flow.run=function(t,e){return new dmx.Flow(e).run(t)},dmx.Component("app",{constructor:function(t,e){this.onload=this.onload.bind(this),dmx.BaseComponent.call(this,t,e)},initialData:{query:{}},attributes:{},methods:{},events:{ready:Event,load:Event},render:function(t){this.parseQuery(),this.$parse(),window.removeEventListener("load",this.onload),window.addEventListener("load",this.onload),dmx.nextTick((function(){this.dispatchEvent("ready")}),this)},update:function(){this.parseQuery()},onload:function(){this.dispatchEvent("load")},parseQuery:function(){var t="";window.location.search?t=window.location.search.substr(1):window.location.hash.indexOf("?")&&(t=window.location.hash.substr(window.location.hash.indexOf("?")+1)).indexOf("#")>0&&(t=t.substr(0,t.indexOf("#")));var e=t.split("&").reduce((function(t,e){var n=e.replace(/\+/g," ").split("=");return n[0]&&(t[decodeURIComponent(n[0])]=decodeURIComponent(n[1]||"")),t}),{}),n=document.querySelector('meta[name="ac:base"]'),i=document.querySelector('meta[name="ac:route"]');if(i&&i.content){var r=[],s=i.content;n&&n.content&&(s=n.content.replace(/\/$/,"")+s);var a=dmx.pathToRegexp(s,r,{end:!1}).exec(decodeURI(window.location.pathname));a&&r.forEach((function(t,n){e[t.name]=a[n+1]}))}this.set("query",e)}}),dmx.Component("form",{tag:"form",attributes:{novalidate:{type:Boolean,default:!1}},methods:{submit:function(t){t?this._submit():this.submit()},reset:function(){this.reset()},validate:function(){this.validate()}},events:{invalid:Event,submit:Event},render:function(t){dmx.BaseComponent.prototype.render.call(this,t),this.$node.noValidate=!0,this.$node.addEventListener("submit",this.onsubmit.bind(this)),this.$node.addEventListener("reset",this.onreset.bind(this))},submit:function(t){if(this.props.novalidate||this.validate())this.dispatchEvent("submit",{cancelable:!0})&&this._submit();else{dmx.requestUpdate(),this.dispatchEvent("invalid");var e=dmx.array(this.$node.elements).find((function(t){if(!t.validity.valid)return!0}));e&&e.focus()}},_submit:function(){HTMLFormElement.prototype.submit.call(this.$node)},reset:function(){HTMLFormElement.prototype.reset.call(this.$node)},validate:function(){return dmx.validate(this.$node)},onsubmit:function(t){t.preventDefault(),this.submit()},onreset:function(t){dmx.validateReset(this.$node),window.grecaptcha&&this.$node.querySelector(".g-recaptcha")&&grecaptcha.reset(),dmx.requestUpdate()}}),dmx.Component("form-element",{constructor:function(t,e){this.updateData=dmx.debounce(this.updateData.bind(this)),dmx.BaseComponent.call(this,t,e)},initialData:{value:"",disabled:!1,validationMessage:"",invalid:!1,focused:!1},attributes:{value:{type:String,default:""},disabled:{type:Boolean,default:!1}},methods:{setValue:function(t){this.setValue(t)},focus:function(){this.focus()},disable:function(t){this.disable(t)},validate:function(){this.validate()}},events:{updated:Event,changed:Event},render:function(t){dmx.BaseComponent.prototype.render.call(this,t),this.$node.value=this.props.value||"",this.$node.disabled=this.props.disabled,this.$node.defaultValue=this.props.value||"",this.$node.addEventListener("input",this.updateData.bind(this)),this.$node.addEventListener("change",this.updateData.bind(this)),this.$node.addEventListener("invalid",this.updateData.bind(this)),this.$node.addEventListener("focus",this.updateData.bind(this)),this.$node.addEventListener("blur",this.updateData.bind(this)),this.updateData()},update:function(t,e){e.has("value")&&(this.$node.defaultValue=this.props.value||"",this.setValue(this.props.value)),t.disabled!=this.props.disabled&&(this.$node.disabled=this.props.disabled),this.updateData()},updateData:function(t){t&&this.$node.dirty&&dmx.validate(this.$node),this.$node.value!==this.data.value&&dmx.nextTick((function(){this.dispatchEvent("updated"),t&&this.dispatchEvent("changed")}),this),this.set("value",this.$node.value),this.set("disabled",this.$node.disabled),this.set("focused",this.$node===document.activeElement),this.$node.dirty&&(this.set("invalid",!this.$node.validity.valid),this.set("validationMessage",this.$node.validationMessage))},setValue:function(t){this.$node.value=t||"",this.updateData()},focus:function(){this.$node.focus()},disable:function(t){this.$node.disabled=!0===t,this.updateData()},validate:function(){dmx.validate(this.$node),this.updateData()}}),dmx.Component("textarea",{extends:"form-element",tag:"textarea",render:function(t){if(!this.props.value){var e=this.$node.value;-1!==e.indexOf("{{")?this.props.value=dmx.parse(e,this):this.props.value=e}dmx.Component("form-element").prototype.render.call(this,t)}}),dmx.Component("input",{extends:"form-element",tag:"input"}),dmx.Component("input-file",{extends:"input",initialData:{file:null},render:function(t){dmx.Component("form-element").prototype.render.call(this,t),this.$node.addEventListener("change",this.onchange.bind(this)),this.$node.form&&this.$node.form.addEventListener("reset",this.onchange.bind(this))},onchange:function(){var t=null;if(this.$node.files.length){var e=this.$node.files[0];t={date:(e.lastModified?new Date(e.lastModified):e.lastModifiedDate).toISOString(),name:e.name,size:e.size,type:e.type,dataUrl:null},-1===e.type.indexOf("image/")||e.reader||(e.reader=new FileReader,e.reader.onload=function(e){t.dataUrl=e.target.result,dmx.requestUpdate()},e.reader.readAsDataURL(e))}this.set("file",t)},setValue:function(){console.warn("Can not set value of a file input!")}}),dmx.Component("input-file-multiple",{extends:"input",initialData:{files:[]},render:function(t){dmx.Component("form-element").prototype.render.call(this,t),this.$node.addEventListener("change",this.onchange.bind(this)),this.$node.form&&this.$node.form.addEventListener("reset",this.onchange.bind(this))},onchange:function(){var t=Array.prototype.slice.call(this.$node.files).map((function(t){var e={date:(t.lastModified?new Date(t.lastModified):t.lastModifiedDate).toISOString(),name:t.name,size:t.size,type:t.type,dataUrl:null};return-1===t.type.indexOf("image/")||t.reader||(t.reader=new FileReader,t.reader.onload=function(t){e.dataUrl=t.target.result,dmx.requestUpdate()},t.reader.readAsDataURL(t)),e}));this.set("files",t)},setValue:function(){console.warn("Can not set value of a file input!")}}),dmx.Component("input-number",{extends:"input",render:function(t){dmx.Component("form-element").prototype.render.call(this,t),this.set("value",+this.props.value)},updateData:function(t){t&&this.$node.dirty&&dmx.validate(this.$node),this.$node.value!==this.data.value&&dmx.nextTick((function(){this.dispatchEvent("updated"),t&&this.dispatchEvent("changed")}),this),this.set("value",this.$node.value?+this.$node.value:null),this.set("disabled",this.$node.disabled),this.$node.dirty&&(this.set("invalid",!this.$node.validity.valid),this.set("validationMessage",this.$node.validationMessage))}}),dmx.Component("button",{extends:"form-element",tag:"button",attributes:{type:{type:String,default:"button",validate:function(t){return/^(button|submit|reset)$/i.test(t)}}},render:function(t){dmx.Component("form-element").prototype.render.call(this,t),this.$node.type=this.props.type}}),dmx.Component("radio",{extends:"form-element",initialData:{checked:!1},tag:"input",attributes:{checked:{type:Boolean,default:!1}},methods:{select:function(t){this.select(t)}},render:function(t){dmx.Component("form-element").prototype.render.call(this,t),this.$node.addEventListener("click",this.updateData.bind(this)),this.$node.type="radio",this.$node.checked=this.props.checked,this.$node.defaultChecked=this.props.checked,this.set("checked",this.props.checked)},update:function(t,e){dmx.Component("form-element").prototype.update.call(this,t,e),t.checked!==this.props.checked&&(this.$node.checked=this.props.checked,this.$node.defaultChecked=this.props.checked),this.updateData()},updateData:function(t){dmx.Component("form-element").prototype.updateData.call(this,t),this.data.checked!=this.$node.checked&&dmx.nextTick((function(){this.dispatchEvent("updated"),t&&this.dispatchEvent("changed")}),this),this.set("checked",this.$node.checked)},select:function(t){this.$node.checked=!1!==t}}),dmx.Component("radio-group",{initialData:{value:""},tag:"div",attributes:{value:{type:String,default:""}},methods:{setValue:function(t){this.setValue(t)}},events:{updated:Event},render:function(t){dmx.BaseComponent.prototype.render.call(this,t),this.setValue(this.props.value)},update:function(t){dmx.BaseComponent.prototype.update.call(this,t),t.value!=this.props.value&&(this.updateValue=!0,dmx.nextTick((function(){this.dispatchEvent("updated")}),this))},updated:function(){this.updateValue&&(this.updateValue=!1,this.setValue(this.props.value,!0));var t=Array.prototype.slice.call(this.$node.querySelectorAll("input[type=radio]")).filter((function(t){return!t.disabled&&t.checked})).map((function(t){return t.value}));dmx.equal(this.data.value,t[0])||(this.set("value",t[0]),dmx.nextTick((function(){this.dispatchEvent("updated")}),this))},setValue:function(t,e){Array.prototype.slice.call(this.$node.querySelectorAll("input[type=radio]")).forEach((function(n){n.checked=n.value==t,e&&(n.defaultChecked=n.checked)})),this.updated()}}),dmx.Component("checkbox",{extends:"form-element",initialData:{checked:!1},tag:"input",attributes:{checked:{type:Boolean,default:!1}},methods:{select:function(t){this.select(t)}},events:{checked:Event,unchecked:Event},render:function(t){dmx.Component("form-element").prototype.render.call(this,t),this.$node.addEventListener("click",this.updateData.bind(this)),this.$node.type="checkbox",this.$node.checked=this.props.checked,this.$node.defaultChecked=this.props.checked,this.set("checked",this.props.checked)},update:function(t,e){dmx.Component("form-element").prototype.update.call(this,t,e),t.checked!==this.props.checked&&(this.$node.checked=this.props.checked,this.$node.defaultChecked=this.props.checked),this.updateData()},updateData:function(t){dmx.Component("form-element").prototype.updateData.call(this,t),this.data.checked!=this.$node.checked&&dmx.nextTick((function(){this.dispatchEvent("updated"),t&&(this.dispatchEvent("changed"),this.dispatchEvent(this.$node.checked?"checked":"unchecked"))}),this),this.set("checked",this.$node.checked)},select:function(t){this.$node.checked=!1!==t}}),dmx.Component("checkbox-group",{initialData:{value:[]},tag:"div",attributes:{value:{type:Array,default:[]}},methods:{setValue:function(t){this.setValue(t)}},events:{updated:Event},render:function(t){dmx.BaseComponent.prototype.render.call(this,t),this.setValue(this.props.value)},update:function(t,e){dmx.BaseComponent.prototype.update.call(this,t,e),e.has("value")&&(this.updateValue=!0)},updated:function(){this.updateValue&&(this.updateValue=!1,this.setValue(this.props.value));var t=Array.prototype.slice.call(this.$node.querySelectorAll("input[type=checkbox]")).filter((function(t){return!t.disabled&&t.checked})).map((function(t){return t.value}));dmx.equal(this.data.value,t)||(this.set("value",t),dmx.nextTick((function(){this.dispatchEvent("updated")}),this))},setValue:function(t,e){Array.isArray(t)||(t=[t]),Array.prototype.slice.call(this.$node.querySelectorAll("input[type=checkbox]")).forEach((function(n){n.checked=t.indexOf(n.value)>-1,e&&(n.defaultChecked=n.checked)})),this.updated()}}),dmx.Component("select",{extends:"form-element",initialData:{selectedIndex:-1,selectedValue:"",selectedText:""},tag:"select",attributes:{options:{type:Array,default:[]},optionText:{type:String,default:"$value"},optionValue:{type:String,default:"$value"}},methods:{setSelectedIndex:function(t){this.$node.selectedIndex=t,this.updateData()}},render:function(t){this.options=[],this.props.value?this.updateValue=!0:this.props.value=this.$node.value,dmx.BaseComponent.prototype.render.call(this,t),this.$node.disabled=this.props.disabled,this.$node.addEventListener("change",this.updateData.bind(this)),this.$node.addEventListener("invalid",this.updateData.bind(this)),this.$node.addEventListener("focus",this.updateData.bind(this)),this.$node.addEventListener("blur",this.updateData.bind(this)),this.renderOptions(),this.updateData()},update:function(t,e){e.has("options")&&(this.renderOptions(),this.updateValue=!0),e.has("value")&&(this.updateValue=!0),t.disabled!=this.props.disabled&&(this.$node.disabled=this.props.disabled),this.updateData()},updated:function(){this.updateValue&&(this.updateValue=!1,this.setValue(this.props.value,!0),this.updateData())},updateData:function(t){dmx.Component("form-element").prototype.updateData.call(this,t);var e=this.$node.selectedIndex;this.set("selectedIndex",e),this.set("selectedValue",this.$node.options[e]&&this.$node.options[e].value||""),this.set("selectedText",this.$node.options[e]&&this.$node.options[e].text||"")},setValue:function(t,e){dmx.array(this.$node.options).forEach((function(n){n.selected=n.value==t,e&&(n.defaultSelected=n.selected)}))},renderOptions:function(){this.options.splice(0).forEach((function(t){dmx.dom.remove(t)})),dmx.repeatItems(this.props.options).forEach((function(t){"object"!=typeof t&&(t={$value:t});var e=document.createElement("option");e.value=dmx.parse(this.props.optionValue,dmx.DataScope(t,this)),e.innerText=dmx.parse(this.props.optionText,dmx.DataScope(t,this)),this.options.push(this.$node.appendChild(e))}),this)}}),dmx.Component("select-multiple",{extends:"select",initialData:{value:[]},attributes:{value:{type:Array,default:null}},methods:{setSelectedIndex:function(t){this.$node.selectedIndex=t,this.updateData()}},update:function(t,e){e.has("options")&&(this.renderOptions(),this.updateValue=!0),e.has("value")&&(this.updateValue=!0),t.disabled!=this.props.disabled&&(this.$node.disabled=this.props.disabled),this.updateData()},updateData:function(t){var e=Array.prototype.slice.call(this.$node.options).filter((function(t){return t.selected})).map((function(t){return t.value}));dmx.equal(this.data.value,e)||dmx.nextTick((function(){this.dispatchEvent("updated"),t&&this.dispatchEvent("changed")}),this),this.set("value",e),this.set("disabled",this.$node.disabled),this.set("invalid",!this.$node.validity.valid),this.set("focused",this.$node===document.activeElement),this.set("validationMessage",this.$node.validationMessage);var n=this.$node.selectedIndex;this.set("selectedIndex",n),this.set("selectedValue",this.$node.options[n]&&this.$node.options[n].value||""),this.set("selectedText",this.$node.options[n]&&this.$node.options[n].text||"")},setValue:function(t,e){Array.isArray(t)||(t=[t]),t=t.map((function(t){return t.toString()})),dmx.array(this.$node.options).forEach((function(n){n.selected=t.indexOf(n.value)>-1,e&&(n.defaultSelected=n.selected)}))}}),dmx.Component("value",{initialData:{value:null},attributes:{value:{default:null}},methods:{setValue:function(t){this.data.value!==t&&(this.set("value",t),dmx.nextTick((function(){this.dispatchEvent("updated")}),this))}},events:{updated:Event},render:function(){this.set("value",this.props.value)},update:function(t,e){e.has("value")&&(this.set("value",this.props.value),dmx.nextTick((function(){this.dispatchEvent("updated")}),this))}}),dmx.Component("repeat",{initialData:{items:[]},attributes:{repeat:{type:[Array,Object,Number],default:[]},key:{type:String,default:""},rerender:{type:Boolean,default:!1}},events:{update:Event,updated:Event},render:function(t){for(this.prevItems=[],this.childKeys={},this.$template=document.createDocumentFragment();this.$node.hasChildNodes();)this.$template.appendChild(this.$node.firstChild);this.update({repeat:[]},new Set(["repeat"]))},update:function(t,e){e.has("repeat")&&(e.has("key")&&(this._rerender=!0),this._willUpdate||(this._willUpdate=!0,dmx.nextTick(this._update,this)))},_update:function(){this._willUpdate=!1,this.dispatchEvent("update"),(this.props.rerender||this._rerender)&&(this._rerender=!1,this._clear());var t=dmx.Component("repeat-item"),e=this.props.repeat,n=dmx.repeatItems(e);if(n.length){if(!this.props.rerender&&this.props.key&&n[0].hasOwnProperty(this.props.key)&&this.prevItems.length){var i,r,s=this.props.key,a=this.prevItems,o=this._clone(n),d=0,u=0,c=a.length-1,h=o.length-1;t:for(;;){for(;a[d][s]===o[u][s];)if(this.childKeys[o[u][s]].set(o[u]),u++,++d>c||u>h)break t;for(;a[c][s]===o[h][s];)if(this.childKeys[o[h][s]].set(o[h]),h--,d>--c||u>h)break t;if(a[c][s]!==o[u][s]){if(a[d][s]!==o[h][s])break;if(r=h+1,this.childKeys[o[h][s]].set(o[h]),this._moveChild(o[h][s],o[r]&&o[r][s]),h--,++d>c||u>h)break}else if(this.childKeys[o[u][s]].set(o[u]),this._moveChild(o[u][s],a[d][s]),u++,d>--c||u>h)break}if(d>c)for(r=h+1;u<=h;)this._insertChild(o[u++],o[r]&&o[r][s]);else if(u>h)for(;d<=c;)this._removeChild(a[d++][s]);else{var l=c-d+1,p=h-u+1,f=a,m=new Array(p).fill(-1),v=!1,x=0,y=0;if(p<=4||l*p<=16){for(w=d;w<=c;w++)if(yi?v=!0:x=i,this.childKeys[o[i][s]].set(o[i]),y++,f[w]=null;break}}else{var g={};for(w=u;w<=h;w++)g[o[w][s]]=w;for(w=d;w<=c;w++)yi?v=!0:x=i,this.childKeys[o[i][s]].set(o[i]),y++,f[w]=null)}if(l===a.length&&0===y)for(this._clear();u0;)null!==f[d]&&(this._removeChild(a[d][s]),w--),d++;if(v){var b=this._lis(m);for(i=b.length-1,w=p-1;w>=0;w--)-1===m[w]?(r=(x=w+u)+1,this._insertChild(o[x],o[r]&&o[r][s])):i<0||w!==b[i]?(r=(x=w+u)+1,this._moveChild(o[x][s],o[r]&&o[r][s])):i--}else if(y!==p)for(w=p-1;w>=0;w--)-1===m[w]&&(r=(x=w+u)+1,this._insertChild(o[x],o[r]&&o[r][s]))}}}else if(this.children.length>n.length&&this.children.splice(n.length).forEach((function(t){t.$destroy()})),this.children.length&&this.children.forEach((function(t,e){t.set(n[e])})),n.length>this.children.length){for(var E=document.createDocumentFragment(),w=this.children.length;wt.data))),dmx.nextTick((()=>this.dispatchEvent("updated")))},_lis:function(t){var e,n,i=t.slice(0),r=[];r.push(0);for(var s=0,a=t.length;s0&&(i[s]=r[e-1]),r[e]=s)}}for(n=r[(e=r.length)-1];e-- >0;)r[e]=n,n=i[n];return r},_clear:function(){this.prevItems=[],this.childKeys={},this.children.splice(0).forEach((function(t){t.$destroy()})),this.$node.innerHTML=""},_insertChild:function(t,e){var n=new(dmx.Component("repeat-item"))(this.$template.cloneNode(!0),this,t);n.$nodes.forEach((function(t){e?this.childKeys[e]?this.$node.insertBefore(t,this.childKeys[e].$nodes[0]):console.warn("(insert) can not insert node before key "+e+"!"):this.$node.appendChild(t),n.$parse(t)}),this),this.childKeys[t[this.props.key]]=n,this.children.push(n)},_moveChild:function(t,e){var n=this.childKeys[t];n?this.childKeys[e]?n.$nodes.forEach((function(t){this.$node.insertBefore(t,this.childKeys[e].$nodes[0])}),this):n.$nodes.forEach((function(t){this.$node.appendChild(t)}),this):console.warn("(move) child with key "+t+" not found!")},_removeChild:function(t){var e=this.childKeys[t];e?(e.$destroy(),this.children.splice(this.children.indexOf(e),1),delete this.childKeys[t]):console.warn("(remove) child with key "+t+" not found!")},_clone:function(t){return dmx.clone(t)}}),dmx.Component("repeat-item",{constructor:function(t,e,n,i){this.parent=e,this.bindings={},this.propBindings={},this.children=[],this.listeners=[],this.props={},this.data=dmx.clone(n||{}),this.seed=e.seed,this.name=i||"repeat",this.$nodes=[];for(var r=0;rthis.fetch())))},abort:function(){this.xhr&&this.xhr.abort()},fetch:function(t){this.xhr.abort(),t=dmx.extend(!0,this.props,t||{}),this._reset(),this.dispatchEvent("start");var e=(t.url.indexOf("?")>-1?"&":"?")+Object.keys(t.params).filter((function(e){return null!=t.params[e]}),this).map((function(e){var n=t.params[e];return"string"==typeof n&&n.startsWith("{{")&&(n=dmx.parse(n,this)),encodeURIComponent(e)+"="+encodeURIComponent(n)}),this).join("&");if(this._url=t.url+e,window.WebviewProxy&&(this._url=window.WebviewProxy.convertProxyUrl(this._url)),this.props.cache){var n=dmx.parse(this.props.cache+'.data["'+this._url+'"]',this);if(n){if(!(Date.now()-n.created>=1e3*t.ttl))return this.set("headers",n.headers||{}),this.set("paging",n.paging||{}),this.set("links",n.links||{}),this.set("data",n.data),this.dispatchEvent("success"),void this.dispatchEvent("done");dmx.parse(this.props.cache+'.remove("'+this._url+'")',this)}}this.set("state",{executing:!0,uploading:!1,processing:!1,downloading:!1});var i=null;"GET"!=this.props.method.toUpperCase()&&("text"==this.props["data-type"]?(t.headers["Content-Type"]||(t.headers["Content-Type"]="application/text"),i=this.props.data.toString()):"json"==this.props["data-type"]?(t.headers["Content-Type"]||(t.headers["Content-Type"]="application/json"),i=JSON.stringify(this.props.data)):"POST"==this.props.method.toUpperCase()?(i=new FormData,Object.keys(this.props.data).forEach((function(t){var e=this.props.data[t];Array.isArray(e)?(/\[\]$/.test(t)||(t+="[]"),e.forEach((function(e){i.append(t,e)}),this)):i.set(t,e)}),this)):(t.headers["Content-Type"]||(t.headers["Content-Type"]="application/text"),i=this.props.data.toString())),this.xhr.open(this.props.method.toUpperCase(),this._url),this.xhr.timeout=1e3*t.timeout,Object.keys(t.headers).forEach((function(e){this.xhr.setRequestHeader(e,t.headers[e])}),this),this.xhr.setRequestHeader("accept","application/json"),this.props.credentials&&(this.xhr.withCredentials=!0);try{this.xhr.send(i)}catch(t){this._done(t)}},_reset:function(){this.set({status:0,links:{},headers:{},state:{executing:!1,uploading:!1,processing:!1,downloading:!1},uploadProgress:{position:0,total:0,percent:0},downloadProgress:{position:0,total:0,percent:0},lastError:{status:0,message:"",response:null}})},_done:function(t){if(this._reset(),t)this.set("lastError",{status:0,message:t.message,response:null}),this.dispatchEvent("error");else{var e=this.xhr.responseText;try{e=JSON.parse(e)}catch(t){if(this.xhr.status<400)return this.set("lastError",{status:0,message:"Response was not valid JSON",response:e}),void this.dispatchEvent("error")}try{var n=this.xhr.getAllResponseHeaders().trim().split(/[\r\n]+/);this.set("headers",n.reduce((function(t,e){var n=e.split(": "),i=n.shift(),r=n.join(": ");return t[i]=r,t}),{}))}catch(t){console.warn("Error parsing response headers",t)}try{var i=Object.keys(this.data.headers).find((function(t){return"link"==t.toLowerCase()}));i&&this.set("links",this.data.headers[i].split(/,\s*]*)>(.*)/),n=e[1],i=e[2].split(";"),r=n.substr(n.indexOf("?")+1);r.indexOf("#")>0&&(r=r.substr(0,r.indexOf("#")));var s=r.split("&").reduce((function(t,e){var n=e.split("=");return n[0]&&(t[decodeURIComponent(n[0])]=decodeURIComponent(n[1]||"")),t}),{});i.shift();var a=i.reduce((function(t,e){var n=e.match(/\s*(.+)\s*=\s*"?([^"]+)"?/);return n&&(t[n[1]]=n[2]),t}),{});return(a=Object.assign({},s,a)).url=n,a}catch(t){return console.warn("Error parsing link header part",t),null}})).filter((function(t){return t&&t.rel})).reduce((function(t,e){return e.rel.split(/\s+/).forEach((function(n){t[n]=Object.assign(e,{rel:n})})),t}),{}))}catch(t){console.warn("Error parsing link header",t)}try{var r={page:1,pages:1,items:0,has:{first:!1,prev:!1,next:!1,last:!1}};if(this.data.links.prev||this.data.links.next){this.data.links.last&&this.data.links.last.page?r.pages=+this.data.links.last.page:this.data.links.prev&&this.data.prev.page&&(r.pages=+this.data.links.prev.page+1);var s=Object.keys(this.data.headers).find((function(t){return"x-total"==(t=t.toLowerCase())||"x-count"==t||"x-total-count"==t}));s&&(r.items=+this.data.headers[s]),this.data.links.prev&&this.data.links.prev.page?r.page=+this.data.links.prev.page+1:this.data.links.next&&this.data.links.next.page&&(r.page=+this.data.links.next.page-1),r.has={first:!!this.data.links.first,prev:!!this.data.links.prev,next:!!this.data.links.next,last:!!this.data.links.last}}this.set("paging",r)}catch(t){console.warn("Error parsing paging",t)}this.set("status",this.xhr.status),this.xhr.status<400?(this.set("data",e),this.dispatchEvent("success"),this.props.cache&&dmx.parse(this.props.cache+'.set("'+this._url+'", { headers: headers, paging: paging, links: links, data: data, created: '+Date.now()+" })",this)):(this.set("lastError",{status:this.xhr.status,message:this.xhr.statusText,response:e}),400==this.xhr.status?this.dispatchEvent("invalid"):401==this.xhr.status?this.dispatchEvent("unauthorized"):403==this.xhr.status?this.dispatchEvent("forbidden"):this.dispatchEvent("error"))}this.dispatchEvent("done")},onload:function(t){this._done()},onabort:function(t){this._reset(),this.dispatchEvent("abort"),this.dispatchEvent("done")},onerror:function(t){this._done({message:"Failed to execute"})},ontimeout:function(t){this._done({message:"Execution timeout"})},onprogress:function(t){return function(e){e.loaded=e.loaded||e.position;var n=e.lengthComputable?Math.ceil(e.loaded/e.total*100):0;this.set("state",{executing:!0,uploading:"upload"==t&&n<100,processing:"upload"==t&&100==n,downloading:"download"==t}),this.set(t+"Progress",{position:e.loaded,total:e.total,percent:n}),this.dispatchEvent(t,{lengthComputable:e.lengthComputable,loaded:e.loaded,total:e.total})}}}),dmx.Component("serverconnect",{extends:"fetch",attributes:{sockets:{type:Boolean,default:!1}},render:function(t){this.props.sockets?(this.eventName=this.props.url.replace(/^(.*?)api\//,""),this.socket=dmx.Socket("/api"),this.socket.on(this.eventName,this.refresh.bind(this)),this.update({})):dmx.Component("fetch").prototype.render.call(this,t)},fetch:function(t){this.props.sockets?this.refresh(t&&t.params):dmx.Component("fetch").prototype.fetch.call(this,t)},refresh:function(t){t=dmx.extend(!0,{},this.props.params,t||{}),this.dispatchEvent("start"),this.set("state",{executing:!0,uploading:!1,processing:!0,downloading:!1}),this.socket.emit(this.eventName,t,function(t){this.set("status",t.status),this.set("data",t.data),this.set("state",{executing:!1,uploading:!1,processing:!1,downloading:!1}),t.status<400?this.dispatchEvent("success"):400==t.status?this.dispatchEvent("invalid"):401==t.status?this.dispatchEvent("unauthorized"):403==t.status?this.dispatchEvent("forbidden"):this.dispatchEvent("error"),this.dispatchEvent("done")}.bind(this))}}),dmx.Component("serverconnect-form",{extends:"form",initialData:{status:0,data:null,headers:{},state:{executing:!1,uploading:!1,processing:!1,downloading:!1},uploadProgress:{position:0,total:0,percent:0},downloadProgress:{position:0,total:0,percent:0},lastError:{status:0,message:"",response:null}},attributes:{timeout:{type:Number,default:0},autosubmit:{type:Boolean,default:!1},params:{type:Object,default:{}},headers:{type:Object,default:{}},"post-data":{type:String,default:"form"},credentials:{type:Boolean,default:!1}},methods:{abort:function(){this.abort()},reset:function(t){this.reset(),t&&(this.abort(),this._reset(),this.set("data",null))}},events:{start:Event,done:Event,error:Event,unauthorized:Event,forbidden:Event,abort:Event,success:Event,upload:ProgressEvent,download:ProgressEvent},$parseAttributes:function(t){dmx.BaseComponent.prototype.$parseAttributes.call(this,t),dmx.dom.getAttributes(t).forEach((function(t){"param"==t.name&&t.argument&&this.$addBinding(t.value,(function(e){this.props.params[t.argument]=e})),"header"==t.name&&t.argument&&this.$addBinding(t.value,(function(e){this.props.headers[t.argument]=e}))}),this)},render:function(t){this.xhr=new XMLHttpRequest,this.xhr.addEventListener("load",this.onload.bind(this)),this.xhr.addEventListener("abort",this.onabort.bind(this)),this.xhr.addEventListener("error",this.onerror.bind(this)),this.xhr.addEventListener("timeout",this.ontimeout.bind(this)),this.xhr.addEventListener("progress",this.onprogress("download").bind(this)),this.xhr.upload&&this.xhr.upload.addEventListener("progress",this.onprogress("upload").bind(this));var e=t.checkValidity;t.dmxExtraData={},t.dmxExtraElements=[],t.checkValidity=function(){for(var n=0;n-1?"&":"?")+r),window.WebviewProxy&&(s=window.WebviewProxy.convertProxyUrl(s)),this.xhr.open(e,s),this.xhr.timeout=1e3*this.props.timeout,Object.keys(this.props.headers).forEach((function(t){this.xhr.setRequestHeader(t,this.props.headers[t])}),this),this.xhr.setRequestHeader("accept","application/json"),this.props.credentials&&(this.xhr.withCredentials=!0);try{this.xhr.send(i)}catch(t){this._done(t)}},_reset:function(){this.set({status:0,headers:{},state:{executing:!1,uploading:!1,processing:!1,downloading:!1},uploadProgress:{position:0,total:0,percent:0},downloadProgress:{position:0,total:0,percent:0},lastError:{status:0,message:"",response:null}})},_done:function(t){if(this._reset(),t)this.set("lastError",{status:0,message:t.message,response:null}),this.dispatchEvent("error");else{var e=this.xhr.responseText;try{e=JSON.parse(e)}catch(t){if(this.xhr.status<400)return this.set("lastError",{status:0,message:"Response was not valid JSON",response:e}),void this.dispatchEvent("error")}try{var n=this.xhr.getAllResponseHeaders().trim().split(/[\r\n]+/);this.set("headers",n.reduce((function(t,e){var n=e.split(": "),i=n.shift(),r=n.join(": ");return t[i]=r,t}),{}))}catch(t){console.warn("Error parsing response headers",t)}if(this.set("status",this.xhr.status),dmx.validateReset&&dmx.validateReset(this.$node),window.grecaptcha&&this.$node.querySelector(".g-recaptcha")&&grecaptcha.reset(),this.xhr.status<400)this.set("data",e),this.dispatchEvent("success");else if(this.set("lastError",{status:this.xhr.status,message:this.xhr.statusText,response:e}),400==this.xhr.status)if(this.dispatchEvent("invalid"),e.form){for(var i in e.form)if(e.form.hasOwnProperty(i)){var r=this.$node.querySelector('[name="'+i+'"]');r&&(r.setCustomValidity(e.form[i]),dmx.requestUpdate(),dmx.bootstrap5forms?dmx.validate.setBootstrap5Message(r,e.form[i]):dmx.bootstrap4forms?dmx.validate.setBootstrap4Message(r,e.form[i]):dmx.bootstrap3forms?dmx.validate.setBootstrapMessage(r,e.form[i]):dmx.validate.setErrorMessage(r,e.form[i]))}}else console.warn("400 error, no form errors in response.",e);else 401==this.xhr.status?this.dispatchEvent("unauthorized"):403==this.xhr.status?this.dispatchEvent("forbidden"):this.dispatchEvent("error")}this.dispatchEvent("done")},onload:function(t){this._done()},onabort:function(t){this._reset(),this.dispatchEvent("abort"),this.dispatchEvent("done")},onerror:function(t){this._done({message:"Failed to execute"})},ontimeout:function(t){this._done({message:"Execution timeout"})},onprogress:function(t){return function(e){e.loaded=e.loaded||e.position;var n=e.lengthComputable?Math.ceil(e.loaded/e.total*100):0;this.set("state",{executing:!0,uploading:"upload"==t&&n<100,processing:"upload"==t&&100==n,downloading:"download"==t}),this.set(t+"Progress",{position:e.loaded,total:e.total,percent:n}),this.dispatchEvent(t,{lengthComputable:e.lengthComputable,loaded:e.loaded,total:e.total})}},_parseJsonForm:function(t){const e={};for(const r of t.elements)if(r.name&&!r.disabled){const t=n(r.name.replace(/\[\]$/,""));let s=e;for(const e of t){const t=r.type;"number"==t?r.value&&(s=i(s,e,s[e.key],+r.value)):"radio"==t||"checkbox"==t?r.getAttribute("value")?r.checked&&(s=i(s,e,s[e.key],r.value)):s=i(s,e,s[e.key],r.checked):s=i(s,e,s[e.key],"select-multiple"==t?Array.from(r.selectedOptions).map((t=>t.value)):r.value)}}return e;function n(t){const e=[],n=t,i=/^\[([^\]]*)\]/,r=/^\d+$/;if(!(t=t.replace(/^([^\[]+)/,((t,n)=>(e.push({type:"object",key:n}),"")))))return e[0].last=!0,e;for(;t;){if(!i.test(t))return{type:"object",key:n,last:!0};t=t.replace(i,((t,n)=>(n?r.test(n)?e.push({type:"array",key:+n}):e.push({type:"object",key:n}):e[e.length-1].append=!0,"")))}for(let t=0,n=e.length;td.length&&i.splice(d.length).forEach((function(t){n.children.splice(n.children.indexOf(t),1),t.$destroy()})),i.length&&i.forEach((function(t,e){t.set(d[e])})),d.length>i.length){for(u=document.createDocumentFragment(),c=i.length;c + +
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+ With faded secondary text +
+
+
+
\ No newline at end of file diff --git a/views/layouts/main.ejs b/views/layouts/main.ejs new file mode 100644 index 0000000..cbd4236 --- /dev/null +++ b/views/layouts/main.ejs @@ -0,0 +1,21 @@ + + + + + + +Untitled Document + + + + + + + + +
+ <%- await include(content, locals); %> +
+ + +